Skip to content

[net11.0] Trimmable element handlers - #29952

Open
simonrozsival wants to merge 43 commits into
net11.0from
dev/simonrozsival/net10.0-use-element-handler-attributes
Open

[net11.0] Trimmable element handlers#29952
simonrozsival wants to merge 43 commits into
net11.0from
dev/simonrozsival/net10.0-use-element-handler-attributes

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Jun 12, 2025

Copy link
Copy Markdown
Member

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!

This is a follow-up to #28357.

Summary

Built-in controls now primarily use [ElementHandler] attributes instead of DI registration for handler resolution. Handler associations are visible at the type level, improving trimmability because unused controls no longer need to be rooted by the default handler-registration table.

Key changes

[ElementHandler] attribute

  • New internal ElementHandlerAttribute on view types declaratively associates a view with its handler.
  • Inherited = false — each concrete type that needs a different handler declares its own attribute.
  • Handler resolution walks the BaseType chain, so derived types without their own attribute inherit the nearest ancestor's handler.
  • Android Material3 controls use control-specific nested attributes so the handler type can be selected conditionally without unconditionally rooting both handler implementations.
  • The same nested-attribute technique selects the handler conditionally where a runtime feature switch applies: Android Material3 controls, and CollectionView on Windows (CollectionViewHandler2 vs. the legacy handler, honoring RuntimeFeature.IsWindowsCollectionView2HandlerEnabled).

Handler resolution in MauiHandlersFactory

Both GetHandler(Type) and GetHandlerType(Type) follow the same resolution order:

  1. Exact DI registration — if the concrete type is registered via AddHandler<Button, MyButtonHandler>(), that wins.
  2. Assignable DI registration — base/interface registrations still override inherited attribute defaults.
  3. [ElementHandler] attribute — walk the type's BaseType chain looking for the attribute.
  4. IContentView fallback — if the type implements IContentView, return ContentViewHandler.

GetHandler() is the primary API — it returns an instantiated IElementHandler. GetHandlerType() returns only the Type and is used by code that needs to compare handler types without instantiating them.

Attribute lookup is cached per view type to avoid repeated reflection walks during handler resolution.

ElementExtensions.ToHandler() / SetHandler()

These methods call GetHandler() directly. When a handler requires constructor parameters, ToHandler() catches the MissingMethodException and falls back to ActivatorUtilities.CreateInstance() with DI injection. This fallback path is cached per type.

Controls mapper remapping

Controls-layer mapper remaps no longer rely on static constructors or RuntimeHelpers.RunClassConstructor.

Controls remapping stays on the Controls virtual view types, matching where the old mapper static constructors lived. ElementHandler.SetVirtualView() invokes an internal Controls remap hook before mapper updates; each remappable control override uses its own Interlocked.CompareExchange field to run once, calls base.RemapForControls() first, then applies its own mapper changes.

This preserves the existing base-before-derived mapper ordering without forcing arbitrary class constructors to run, without putting Controls-specific remap logic on Core handlers, and without handwritten per-control handler-attribute classes.

Non-mapper command dependency setup (CommandProperty.DependsOn(...)) remains in the relevant control type initialization so binding behavior is independent of handler creation.

Cleanup

  • Removed IElementHandlerWithAndroidContext and ElementHandlerWithAndroidContextAttribute.
  • Removed dead RemapForControls(this MauiAppBuilder) and old RemapForControls() / RemapIfNeeded() patterns.
  • Removed RemappingHelper and the static-constructor-based mapper-remap helper path.
  • Removed an empty Layout.Mapper.cs remap file.
  • Fixed Slider not having mapper remapping coverage.
  • Restored IMauiHandlersFactory.GetHandler() to its original signature.
  • Removed unused MauiContext / HandlersContextStub declarations from tests and benchmarks.
  • Matched cell compatibility handler attributes to the platforms where those renderers actually exist.
  • Kept command dependency registration separate from mapper remapping so it is not delayed until handler attachment.
  • Migrated HybridWebView to [ElementHandler(typeof(HybridWebViewHandler))] and removed the RuntimeFeature.IsHybridWebViewSupported feature switch, now that [AOT] Make HybridWebView AOT safe using source generator #35626 made HybridWebView NativeAOT-safe via a source generator. IHybridWebViewTaskManager is now registered unconditionally.
  • Preserved the default Windows CollectionViewHandler2 selection during the migration (a Windows-specific [ElementHandler] attribute honors RuntimeFeature.IsWindowsCollectionView2HandlerEnabled).
Copilot AI review requested due to automatic review settings June 12, 2025 07:43
@simonrozsival
simonrozsival requested a review from a team as a code owner June 12, 2025 07:43

This comment was marked as outdated.

Comment thread src/Core/src/RuntimeFeature.cs
@simonrozsival
simonrozsival marked this pull request as draft June 12, 2025 08:06
@mattleibow

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.
@PureWeen PureWeen added this to the .NET 10.0-preview6 milestone Jun 17, 2025
@PureWeen PureWeen moved this from Todo to In Progress in MAUI SDK Ongoing Jun 17, 2025
@simonrozsival
simonrozsival force-pushed the dev/simonrozsival/net10.0-use-element-handler-attributes branch from 33afe18 to c49e57e Compare June 18, 2025 11:53
@PureWeen PureWeen added platform/macos macOS / Mac Catalyst platform/windows platform/android platform/ios area-architecture Issues with code structure, SDK structure, implementation details labels Aug 7, 2025
@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot there are merge conflicts in this branch. please resolve them. When you're done, write summary of what this PR does and what functionality is missing to fully convert the "old" way of implementing handlers to the "new" way.

Copilot AI commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

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

@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot oops, try now!

Copilot AI commented Nov 25, 2025

Copy link
Copy Markdown
Contributor

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

@simonrozsival

Copy link
Copy Markdown
Member Author

@copilot there are several errors popping up:

  • src\Controls\src\Core\Shell\Shell.cs(29,25): Error CS0246: The type or namespace name 'ShellHandler' could not be found (are you missing a using directive or an assembly reference?)
  • src\Controls\src\Core\Shell\ShellItem.cs(18,25): Error CS0246: The type or namespace name 'ShellItemHandler' could not be found (are you missing a using directive or an assembly reference?)
  • buildtasks/Microsoft.Maui.Controls.Build.Tasks.After.targets(15,3): error MSB4024: The imported project file "/mnt/vss/_work/1/s/.buildtasks/Microsoft.Maui.Controls.targets" could not be loaded. Name cannot begin with the '<' character, hexadecimal value 0x3C. Line 325, position 2. [/mnt/vss/_work/1/s/src/BlazorWebView/tests/DeviceTests/MauiBlazorWebView.DeviceTests.csproj]

Fix them and make sure the product can be built.

Copilot AI commented Dec 1, 2025

Copy link
Copy Markdown
Contributor

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

Validate attribute handler types consistently, make the mapper test self-contained, and correct the no-op handler registration documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: a8d4a631-257f-49c2-b002-eaf82ac7a057
Copilot AI review requested due to automatic review settings July 14, 2026 16:34
@simonrozsival

Copy link
Copy Markdown
Member Author

@PureWeen @kubaflo I pushed 7d6af056f3 to address the final review feedback:

  • validate attribute-resolved handler types consistently in both GetHandler and GetHandlerType, with focused regression coverage;
  • make the PlatformContainerViewChanged mapper test self-contained under lazy remapping;
  • correct the AddMauiControlsHandlers XML documentation to describe its no-op behavior.

The earlier substantive handler-selection, DI-precedence, and remap-concurrency findings are also addressed at the current head. Please take another look when the fresh CI run completes.

@simonrozsival

Copy link
Copy Markdown
Member Author

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

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
@kubaflo

This comment has been minimized.

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jul 14, 2026
Copilot stopped reviewing on behalf of simonrozsival due to an error July 14, 2026 16:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Comments suppressed due to low confidence (2)

src/Controls/src/Core/TimePicker/TimePicker.cs:1

  • [TimePickerHandler] will not bind to the nested TimePickerHandlerAttribute type (TimePicker.TimePickerHandlerAttribute). Attribute type name lookup for a type-level attribute won’t resolve nested types here, so this is likely a compile-time or runtime resolution bug. Fix by moving the custom attribute types (e.g., TimePickerHandlerAttribute, SwitchHandlerAttribute, LabelHandlerAttribute, etc.) to namespace scope (internal is fine) so [TimePickerHandler] resolves correctly, or apply the fully qualified attribute type if you keep it non-nested.
    src/Core/src/Hosting/Internal/MauiHandlersFactory.cs:1
  • Only MissingMethodException is wrapped with an actionable HandlerNotFoundException. Other common activation failures (e.g., abstract handler type => MemberAccessException, ctor throws => TargetInvocationException) will currently bubble out with less context than the MissingMethod path. Consider catching these activation-related exceptions and wrapping them in HandlerNotFoundException with the same {viewType} / {handlerType} context so failures remain actionable and consistent.
  • Files reviewed: 123/125 changed files
  • Comments generated: 0 new
Copilot AI review requested due to automatic review settings July 14, 2026 16:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 123/125 changed files
  • Comments generated: 2
Comment on lines +89 to +93
catch (HandlerNotFoundException ex) when (ex.InnerException is MissingMethodException)
{
handler = viewType.CreateTypeWithInjection(context);
if (handler != null)
handlersWithConstructors.Add(view.GetType());
Comment on lines +143 to +147
throw new HandlerNotFoundException(
$"Unable to create the {nameof(IElementHandler)} {handlerType} declared by {nameof(ElementHandlerAttribute)} for {viewType}. " +
$"Handlers declared with {nameof(ElementHandlerAttribute)} must have a public parameterless constructor when resolved directly through {nameof(IMauiHandlersFactory.GetHandler)}. " +
$"Use `Microsoft.Maui.Hosting.MauiHandlersCollectionExtensions.AddHandler` to register handlers that require constructor arguments, or resolve through `ToHandler()` so constructor arguments can come from the DI container.",
ex);

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 2 findings

See inline comments for details.

});
// Ensure the handlers factory is registered even though most built-in handlers are resolved
// via [ElementHandler] attributes on the view types.
builder.ConfigureMauiHandlers(configureDelegate: null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Backward Compatibility — Switching the built-in Controls defaults to attribute-only registration means TryAddHandler<Button, LibraryButtonHandler>() now succeeds after UseMauiApp() instead of no-oping against the existing MAUI default. Because exact DI registrations win over [ElementHandler] defaults, libraries that defensively call TryAddHandler can now globally replace built-in handlers. Please preserve the old TryAddHandler semantics for exact attributed built-ins, or add an explicit compatibility decision plus regression test.

{
lock (s_controlsMapperRemapLock)
{
remappable.RemapForControls();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Handler Mapper and Property Patterns — Running the Controls remap from the first SetVirtualView() happens after app/library startup mapper customizations. A common UseMauiApp().ConfigureMauiHandlers(_ => ButtonHandler.Mapper.AppendToMapping(nameof(Button.Text), ...)) customization can be discarded when this late remap calls ReplaceMapping for Button.Text, because ReplaceMapping does not invoke the previous mapping for matching Button handlers. Please ensure Controls remaps run before user mapper customizations, or preserve prior append/prepend mappings when the late remap replaces a key.

MauiBot

This comment was marked as outdated.

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Adversarial re-review (round 3) — HEAD 7d6af0 ✅ prior findings resolved

Ran a fresh 3-model adversarial pass (claude-opus-4.8, gpt-5.6-sol, claude-sonnet-5) focused on the 7 commits pushed since my round-2 review. Both of my prior open findings are fixed at this head, verified against source.

⚠️ Stale-head correction

My round-2 review was posted against 4fd577, but the real PR head was already 7d6af0 (a 7-commit descendant). So my round-2 "still open" claims were against superseded code. At the current head, both are resolved — apologies for the confusion.

Prior findings — status at 7d6af0

Finding (prior round) Status Verification
RemapForControls() first-attach race on the shared static mapper Resolved SetVirtualView now wraps the remap in lock (s_controlsMapperRemapLock). The winner runs the full Interlocked.CompareExchange + mapper mutation inside the lock; a losing thread blocks until release, then observes the fully-mutated mapper (lock release/acquire = full barrier) and early-returns. Global scope is correct here — base.RemapForControls() mutates the process-wide shared ViewMapper, so a per-type lock would leave that unprotected. No deadlock: all RemapForControls overrides were audited and none re-enter SetVirtualView; and System.Threading.Lock is reentrant/recursive anyway, so same-thread re-entry would not self-deadlock.
ToHandler() ActivatorUtilities fallback broken for [ElementHandler] handlers needing ctor args Resolved New catch (HandlerNotFoundException ex) when (ex.InnerException is MissingMethodException) routes to CreateTypeWithInjection (DI). The filter is precise and does not over-catch: the new non-handler-type validation error throws with InnerException == null, so it correctly propagates. New test ToHandlerUsesActivatorUtilitiesForElementHandlerAttributeWithoutDefaultConstructor asserts Assert.Same(dependency, handler.Dependency) — proving real DI injection, not just non-null.

Also verified clean

  • GetValidatedHandlerType — validates the attribute's type implements IElementHandler, throws an actionable HandlerNotFoundException (null inner) otherwise; DAM PublicConstructors annotation threads correctly through to Activator/ActivatorUtilities. Covered by two new tests on both the GetHandler and GetHandlerType paths. Making GetHandlerType throw (instead of returning an invalid type that fails later deep in DI) is a desirable behavior change, not a regression.
  • inherit: false on the BaseType-walk attribute lookup — behaviorally identical (ElementHandlerAttribute is [AttributeUsage(Inherited = false)]); a clarity improvement.
  • Netstandard Lock alias (using Lock = System.ObjectMonitor) under #if NET9_0_OR_GREATER — sound.
  • Material3 test hardeningDisableParallelization=true genuinely serializes the process-global AppContext switch against the rest of the assembly; no residual cross-class race since the switch is only touched in that one class.

Remaining note (non-blocking, pre-existing)

One ⚠️ surfaced by all three reviewers — see the inline comment on handlersWithConstructors. It's a pre-existing unsynchronized-HashSet issue, not introduced here; the delta just adds a second writer path to it. Real-world impact is low because ToHandler/SetVirtualView are normally UI-thread-bound. Optional to harden in this PR since it's now touched twice as often.

Net: the 7-commit delta is high quality and closes both of my prior findings with regression tests. No new blocking (❌) defects. Round-3 verdict: clean (one optional pre-existing hardening note). CI status was out of scope for this pass.

(Comment-only review — approval is a human decision.)

{
handler = viewType.CreateTypeWithInjection(context);
if (handler != null)
handlersWithConstructors.Add(view.GetType());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⚠️ Race Condition (pre-existing, non-blocking) — This new catch adds a second unsynchronized writer to the static handlersWithConstructors HashSet<Type> (declared at line 35; also written in the MissingMethodException catch just above, and read at line 78). HashSet<T> is not thread-safe: concurrent Add/Contains from parallel first-time ToHandler() calls can corrupt the set's internal buckets (not merely miss an entry), potentially throwing during resolution.

This predates the PR, so it's not a regression and not a blocker — ToHandler() is normally UI-thread-bound so a live trigger is unlikely. But since the delta now doubles the write paths, this is a reasonable spot to harden: switch to ConcurrentDictionary<Type, byte> or guard all three accesses with a lock. All three round-3 reviewers independently flagged this.

Restore the current Android Shell, ShellItem, and ShellSection handlers after migrating built-in registrations to attributes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4542e8a1-05ad-484c-9852-9e86c3a5c250

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 123/125 changed files
  • Comments generated: 1
Comment on lines +62 to +66
if (VirtualView is IControlsMapperRemappable remappable)
{
lock (s_controlsMapperRemapLock)
{
remappable.RemapForControls();
@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).
@kubaflo

This comment has been minimized.

1 similar comment
@kubaflo

This comment has been minimized.

@MauiBot

This comment has been minimized.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

ℹ️ The review agent did not produce a full summary on this run (an infrastructure issue on the CI agent), but the deep UI tests completed — their results are below. Re-comment /review for a fresh full review.

Deep UI tests — 1049 passed, 3 failed across 16 categories on platform-pool agent (replaces in-process counts above).

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
Border 48/48 ✓
BoxView 24/25 ✓
Button 67/69 ✓
CheckBox 23/23 ✓
DatePicker 44/44 ✓
Editor 66/66 ✓
Entry 111/113 (1 ❌)
FlyoutPage 60/62 (2 ❌)
Frame 6/6 ✓
ImageButton 52/52 ✓
IndicatorView 39/39 ✓
Label 96/98 ✓
Layout 191/194 ✓
ListView 108/109 ✓
Navigation 88/89 ✓
Page 26/26 ✓
🔍 AI analysis of failures — PR-related vs unrelated

🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.

Likely PR-related: one or more failures appear connected to this PR's changes.

  • ✗ PR-related — Entry clear-button interaction on Android (~1 test): the PR changes shared Entry handler resolution/remapping code compiled into the Android run, and the failure is an Appium null-click while exercising the Entry clear-button theme behavior.
  • ✗ PR-related — FlyoutPage gesture opening on Android (~2 tests): the PR changes shared FlyoutPage and mapper code, and both failures assert that the Android flyout gesture no longer opens the flyout.

Strongest signal: every failing category maps directly to shared control files modified by the PR and compiled for Android, with no clear infrastructure-only error pattern.

Entry — 1 failed test
EntryClearButtonColorShouldUpdateOnThemeChange
System.NullReferenceException : Object reference not set to an instance of an object.
at UITest.Appium.HelperExtensions.Click(IUIElement element) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 416
   at UITest.Appium.HelperExtensions.<>c__DisplayClass2_0.<Tap>b__0() in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 36
   at UITest.Appium.HelperExtensions.<>c__DisplayClass186_0.<RunWithTimeout>b__0() in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 3079
   at System.Threading.Tasks.Task`1.InnerInvoke()
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Tas
...
FlyoutPage — 2 failed tests
VerifyFlyoutPage_IsGestureEnabled
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 92

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 92
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@simonrozsival — new AI review results are available based on this last commit: 3fb4cbd.

Gate Inconclusive Confidence Medium Platform Android


🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix

Gate Result: ⚠️ INCONCLUSIVE

Platform: ANDROID · Base: net11.0 · Merge base: 381ab4fe

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 CommandSourceTests CommandSourceTests 🛠️ BUILD ERROR 🔍 NO MATCH
🧪 HostBuilderHandlerTests HostBuilderHandlerTests 🛠️ BUILD ERROR ✅ PASS — 20s
🧪 VisualElementTests VisualElementTests 🛠️ BUILD ERROR ✅ PASS — 20s
📱 Material3HandlerResolutionTestsCollection (ResolvesMaterial3HandlerWhenFeatureSwitchEnabled, InstantiatesMaterial3HandlerAndPlatformViewWhenFeatureSwitchEnabled) Category=Mapper ⚠️ ENV ERROR ⚠️ ENV ERROR
📱 MapperTests MapperTests ⚠️ ENV ERROR ⚠️ ENV ERROR
🔴 Without fix — 🧪 CommandSourceTests: 🛠️ BUILD ERROR · 73s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/VisualElementTests.cs(93,4): error CS0176: Member 'VisualElement.RemapForControls()' cannot be accessed with an instance reference; qualify it with a type name instead [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 CommandSourceTests: 🔍 NO MATCH · 53s

(no coded error found; showing last 1200 chars)

i.Controls.Maps.dll
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net11.0/Microsoft.Maui.Controls.Xaml.dll
  TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
  Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net11.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net11.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v11.0)
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 11.0.0-preview.7.26365.101)
[xUnit.net 00:00:00.17]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.06]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.07]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.09]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
No test matches the given testcase filter `CommandSourceTests` in /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net11.0/Microsoft.Maui.Controls.Core.UnitTests.dll


🔴 Without fix — 🧪 HostBuilderHandlerTests: 🛠️ BUILD ERROR · 23s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/VisualElementTests.cs(93,4): error CS0176: Member 'VisualElement.RemapForControls()' cannot be accessed with an instance reference; qualify it with a type name instead [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 HostBuilderHandlerTests: PASS ✅ · 20s

(no coded error found; showing last 1200 chars)

 VariousControlsGetCorrectHandler(viewType: typeof(Microsoft.Maui.Controls.IndicatorView), handlerType: typeof(Microsoft.Maui.Handlers.IndicatorViewHandler)) [1 ms]
  Passed VariousControlsGetCorrectHandler(viewType: typeof(Microsoft.Maui.Controls.Shapes.Ellipse), handlerType: typeof(Microsoft.Maui.Handlers.ShapeViewHandler)) [< 1 ms]
  Passed VariousControlsGetCorrectHandler(viewType: typeof(Microsoft.Maui.Controls.BoxView), handlerType: typeof(Microsoft.Maui.Controls.Handlers.BoxViewHandler)) [< 1 ms]
  Passed VariousControlsGetCorrectHandler(viewType: typeof(Microsoft.Maui.Controls.ProgressBar), handlerType: typeof(Microsoft.Maui.Handlers.ProgressBarHandler)) [< 1 ms]
  Passed VariousControlsGetCorrectHandler(viewType: typeof(Microsoft.Maui.Controls.Shapes.RoundRectangle), handlerType: typeof(Microsoft.Maui.Controls.Handlers.RoundRectangleHandler)) [< 1 ms]
  Passed RegisteredBaseControlHandlerOverridesInheritedElementHandlerAttribute [3 ms]
[xUnit.net 00:00:03.40]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed CanSpecifyHandler [< 1 ms]
  Passed DefaultHandlersAreRegistered [3 ms]

Test Run Successful.
Total tests: 47
     Passed: 47
 Total time: 4.2807 Seconds

🔴 Without fix — 🧪 VisualElementTests: 🛠️ BUILD ERROR · 20s

Error-relevant lines (filtered from the build log):

/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/VisualElementTests.cs(93,4): error CS0176: Member 'VisualElement.RemapForControls()' cannot be accessed with an instance reference; qualify it with a type name instead [/home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj]
🟢 With fix — 🧪 VisualElementTests: PASS ✅ · 20s

(no coded error found; showing last 1200 chars)

of(Microsoft.Maui.Controls.LinearGradientBrush), defaultCtor: True) [96 ms]
  Passed HandlerDoesPropagateWidthChangesWhenUpdatedDuringSizedChanged [201 ms]
  Passed If HeightRequest has been set and is reset to -1, the Core Height should return to being Unset [11 ms]
  Passed PlatformContainerViewChangedFiresWhenContainerViewIsMapped [238 ms]
  Passed ShadowDoesNotLeak [115 ms]
  Passed RectangleGeometrySubscribed [100 ms]
[xUnit.net 00:00:04.86]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed BindingContextPropagatesToBackground [36 ms]
  Passed WidthAndHeightRequestPropagateToHandler [17 ms]
  Passed GradientBrushSubscribed [116 ms]
  Passed ShadowSubscribed [76 ms]
  Passed HandlerDoesntPropagateWidthChangesDuringBatchUpdates [9 ms]
  Passed If WidthRequest has been set and is reset to -1, the Core Width should return to being Unset [< 1 ms]
  Passed FocusedElementGetsFocusedVisualState [29 ms]
  Passed ClipDoesNotLeak(type: typeof(Microsoft.Maui.Controls.Shapes.RectangleGeometry)) [91 ms]
  Passed ClipDoesNotLeak(type: typeof(Microsoft.Maui.Controls.Shapes.EllipseGeometry)) [70 ms]

Test Run Successful.
Total tests: 18
     Passed: 18
 Total time: 5.8212 Seconds

🔴 Without fix — 📱 Material3HandlerResolutionTestsCollection (ResolvesMaterial3HandlerWhenFeatureSwitchEnabled, InstantiatesMaterial3HandlerAndPlatformViewWhenFeatureSwitchEnabled): ⚠️ ENV ERROR · 1504s

(no coded error found; showing last 1200 chars)


      {
        "version": 1,
        "machineName": "runnervm6ochl",
        "exitCode": 80,
        "exitCodeName": "APP_CRASH",
        "platform": "android",
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "adb-logcat-com.microsoft.maui.controls.devicetests-default.log",
            "type": "logcat"
          },
          {
            "name": "adb-bugreport-com.microsoft.maui.controls.devicetests.zip",
            "type": "bugreport"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[41m�[30mfail�[39m�[22m�[49m: Waiting for command timed out: execution may be compromised
�[41m�[30mfail�[39m�[22m�[49m: Error: Exit code: -2
      Std out:
      
      
      
XHarness exit code: 80 (APP_CRASH)
  Tests completed with exit code: 80

🟢 With fix — 📱 Material3HandlerResolutionTestsCollection (ResolvesMaterial3HandlerWhenFeatureSwitchEnabled, InstantiatesMaterial3HandlerAndPlatformViewWhenFeatureSwitchEnabled): ⚠️ ENV ERROR · 708s

(no coded error found; showing last 1200 chars)

i.controls.devicetests.zip
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervm6ochl",
        "exitCode": 80,
        "exitCodeName": "APP_CRASH",
        "platform": "android",
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "adb-logcat-com.microsoft.maui.controls.devicetests-default.log",
            "type": "logcat"
          },
          {
            "name": "adb-bugreport-com.microsoft.maui.controls.devicetests.zip",
            "type": "bugreport"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.controls.devicetests
XHarness exit code: 80 (APP_CRASH)
  Tests completed with exit code: 80

🔴 Without fix — 📱 MapperTests: ⚠️ ENV ERROR · 620s

(no coded error found; showing last 1200 chars)

i.controls.devicetests.zip
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervm6ochl",
        "exitCode": 80,
        "exitCodeName": "APP_CRASH",
        "platform": "android",
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "adb-logcat-com.microsoft.maui.controls.devicetests-default.log",
            "type": "logcat"
          },
          {
            "name": "adb-bugreport-com.microsoft.maui.controls.devicetests.zip",
            "type": "bugreport"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.controls.devicetests
XHarness exit code: 80 (APP_CRASH)
  Tests completed with exit code: 80

🟢 With fix — 📱 MapperTests: ⚠️ ENV ERROR · 677s

(no coded error found; showing last 1200 chars)

i.controls.devicetests.zip
�[40m�[32minfo�[39m�[22m�[49m: <<XHARNESS_RESULT_START>>
      {
        "version": 1,
        "machineName": "runnervm6ochl",
        "exitCode": 80,
        "exitCodeName": "APP_CRASH",
        "platform": "android",
        "device": "emulator-5554",
        "deviceOsVersion": "API 30",
        "architecture": "x86_64",
        "files": [
          {
            "name": "adb-logcat-com.microsoft.maui.controls.devicetests-default.log",
            "type": "logcat"
          },
          {
            "name": "adb-bugreport-com.microsoft.maui.controls.devicetests.zip",
            "type": "bugreport"
          }
        ]
      }
      <<XHARNESS_RESULT_END>>
�[40m�[32minfo�[39m�[22m�[49m: Attempting to remove apk 'com.microsoft.maui.controls.devicetests'..
�[40m�[37mdbug�[39m�[22m�[49m: Executing command: '/home/vsts/.nuget/packages/microsoft.dotnet.xharness.cli/11.0.0-prerelease.26230.4/runtimes/any/native/adb/linux/adb -s emulator-5554 uninstall com.microsoft.maui.controls.devicetests'
�[40m�[32minfo�[39m�[22m�[49m: Successfully uninstalled com.microsoft.maui.controls.devicetests
XHarness exit code: 80 (APP_CRASH)
  Tests completed with exit code: 80

⚠️ Failure Details (8 tests)
  • 🛠️ CommandSourceTests without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/VisualElementTests.cs(93,4): error CS0176: Member 'VisualElement.RemapForControls()' cannot be accessed with an instance reference; qualify it wit...
  • 🛠️ HostBuilderHandlerTests without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/VisualElementTests.cs(93,4): error CS0176: Member 'VisualElement.RemapForControls()' cannot be accessed with an instance reference; qualify it wit...
  • 🛠️ VisualElementTests without fix: build failed before tests could run
    • /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/VisualElementTests.cs(93,4): error CS0176: Member 'VisualElement.RemapForControls()' cannot be accessed with an instance reference; qualify it wit...
  • ⚠️ Material3HandlerResolutionTestsCollection (ResolvesMaterial3HandlerWhenFeatureSwitchEnabled, InstantiatesMaterial3HandlerAndPlatformViewWhenFeatureSwitchEnabled) without fix: App crashed during test run (XHarness exit 80 APP_CRASH)
  • ⚠️ MapperTests without fix: App crashed during test run (XHarness exit 80 APP_CRASH)
  • 🔍 CommandSourceTests with fix: test filter matched 0 tests
    • filter: CommandSourceTests
  • ⚠️ Material3HandlerResolutionTestsCollection (ResolvesMaterial3HandlerWhenFeatureSwitchEnabled, InstantiatesMaterial3HandlerAndPlatformViewWhenFeatureSwitchEnabled) with fix: App crashed during test run (XHarness exit 80 APP_CRASH)
  • ⚠️ MapperTests with fix: App crashed during test run (XHarness exit 80 APP_CRASH)
📁 Fix files reverted (114 files)
  • src/Controls/samples/Controls.Sample/Controls/BordelessEntry/BordelessEntryServiceBuilder.cs
  • src/Controls/src/Core/ActivityIndicator/ActivityIndicator.cs
  • src/Controls/src/Core/Application/Application.Mapper.cs
  • src/Controls/src/Core/Application/Application.cs
  • src/Controls/src/Core/Border/Border.cs
  • src/Controls/src/Core/BoxView/BoxView.cs
  • src/Controls/src/Core/Button/Button.Mapper.cs
  • src/Controls/src/Core/Button/Button.cs
  • src/Controls/src/Core/Cells/Cell.cs
  • src/Controls/src/Core/Cells/EntryCell.cs
  • src/Controls/src/Core/Cells/ImageCell.cs
  • src/Controls/src/Core/Cells/SwitchCell.cs
  • src/Controls/src/Core/Cells/TextCell.cs
  • src/Controls/src/Core/Cells/ViewCell.cs
  • src/Controls/src/Core/CheckBox/CheckBox.Mapper.cs
  • src/Controls/src/Core/CheckBox/CheckBox.cs
  • src/Controls/src/Core/ContentPage/ContentPage.Mapper.cs
  • src/Controls/src/Core/ContentView/ContentView.cs
  • src/Controls/src/Core/DatePicker/DatePicker.Mapper.cs
  • src/Controls/src/Core/DatePicker/DatePicker.cs
  • src/Controls/src/Core/Editor/Editor.Mapper.cs
  • src/Controls/src/Core/Editor/Editor.cs
  • src/Controls/src/Core/Element/Element.Mapper.cs
  • src/Controls/src/Core/Element/Element.cs
  • src/Controls/src/Core/Entry/Entry.Mapper.cs
  • src/Controls/src/Core/Entry/Entry.cs
  • src/Controls/src/Core/FlyoutPage/FlyoutPage.Mapper.cs
  • src/Controls/src/Core/FlyoutPage/FlyoutPage.cs
  • src/Controls/src/Core/Frame/Frame.cs
  • src/Controls/src/Core/GraphicsView/GraphicsView.cs
  • src/Controls/src/Core/Hosting/AppHostBuilderExtensions.cs
  • src/Controls/src/Core/HybridWebView/HybridWebView.cs
  • src/Controls/src/Core/Image/Image.cs
  • src/Controls/src/Core/ImageButton/ImageButton.Mapper.cs
  • src/Controls/src/Core/ImageButton/ImageButton.cs
  • src/Controls/src/Core/IndicatorView/IndicatorView.cs
  • src/Controls/src/Core/Items/CarouselView.cs
  • src/Controls/src/Core/Items/CollectionView.cs
  • src/Controls/src/Core/Label/Label.Mapper.cs
  • src/Controls/src/Core/Label/Label.cs
  • src/Controls/src/Core/Layout/Layout.Mapper.cs
  • src/Controls/src/Core/Layout/Layout.cs
  • src/Controls/src/Core/ListView/ListView.cs
  • src/Controls/src/Core/Menu/MenuBar.cs
  • src/Controls/src/Core/Menu/MenuBarItem.cs
  • src/Controls/src/Core/Menu/MenuFlyout.cs
  • src/Controls/src/Core/Menu/MenuFlyoutItem.cs
  • src/Controls/src/Core/Menu/MenuFlyoutSeparator.cs
  • src/Controls/src/Core/Menu/MenuFlyoutSubItem.cs
  • src/Controls/src/Core/NavigationPage/NavigationPage.Mapper.cs
  • src/Controls/src/Core/NavigationPage/NavigationPage.cs
  • src/Controls/src/Core/Page/Page.cs
  • src/Controls/src/Core/Picker/Picker.Mapper.cs
  • src/Controls/src/Core/Picker/Picker.cs
  • src/Controls/src/Core/ProgressBar/ProgressBar.cs
  • src/Controls/src/Core/RadioButton/RadioButton.Mapper.cs
  • src/Controls/src/Core/RadioButton/RadioButton.cs
  • src/Controls/src/Core/RefreshView/RefreshView.Mapper.cs
  • src/Controls/src/Core/RefreshView/RefreshView.cs
  • src/Controls/src/Core/ScrollView/ScrollView.Mapper.cs
  • src/Controls/src/Core/ScrollView/ScrollView.cs
  • src/Controls/src/Core/SearchBar/SearchBar.Mapper.cs
  • src/Controls/src/Core/SearchBar/SearchBar.cs
  • src/Controls/src/Core/Shape/Shape.Mapper.cs
  • src/Controls/src/Core/Shapes/Ellipse.cs
  • src/Controls/src/Core/Shapes/Line.cs
  • src/Controls/src/Core/Shapes/Path.cs
  • src/Controls/src/Core/Shapes/Polygon.cs
  • src/Controls/src/Core/Shapes/Polyline.cs
  • src/Controls/src/Core/Shapes/Rectangle.cs
  • src/Controls/src/Core/Shapes/RoundRectangle.cs
  • src/Controls/src/Core/Shell/Shell.cs
  • src/Controls/src/Core/Shell/ShellContent.cs
  • src/Controls/src/Core/Shell/ShellItem.cs
  • src/Controls/src/Core/Shell/ShellSection.cs
  • src/Controls/src/Core/Slider/Slider.Mapper.cs
  • src/Controls/src/Core/Slider/Slider.cs
  • src/Controls/src/Core/Stepper/Stepper.Mapper.cs
  • src/Controls/src/Core/Stepper/Stepper.cs
  • src/Controls/src/Core/SwipeView/SwipeItem.cs
  • src/Controls/src/Core/SwipeView/SwipeItemView.cs
  • src/Controls/src/Core/SwipeView/SwipeView.Mapper.cs
  • src/Controls/src/Core/SwipeView/SwipeView.cs
  • src/Controls/src/Core/Switch/Switch.cs
  • src/Controls/src/Core/TabbedPage/TabbedPage.Mapper.cs
  • src/Controls/src/Core/TabbedPage/TabbedPage.cs
  • src/Controls/src/Core/TableView/TableView.cs
  • src/Controls/src/Core/TemplatedView/TemplatedView.cs
  • src/Controls/src/Core/TimePicker/TimePicker.Mapper.cs
  • src/Controls/src/Core/TimePicker/TimePicker.cs
  • src/Controls/src/Core/Toolbar/Toolbar.Mapper.cs
  • src/Controls/src/Core/Toolbar/Toolbar.cs
  • src/Controls/src/Core/VisualElement/VisualElement.Mapper.cs
  • src/Controls/src/Core/WebView/WebView.Mapper.cs
  • src/Controls/src/Core/WebView/WebView.cs
  • src/Controls/src/Core/Window/Window.Mapper.cs
  • src/Controls/src/Core/Window/Window.cs
  • src/Controls/src/Xaml/Hosting/AppHostBuilderExtensions.cs
  • src/Core/src/Handlers/Button/ButtonHandler.Windows.cs
  • src/Core/src/Handlers/DatePicker/DatePickerHandler.Windows.cs
  • src/Core/src/Handlers/Element/ElementHandler.cs
  • src/Core/src/Handlers/Element/ElementHandlerAttribute.cs
  • src/Core/src/Handlers/Element/RemappingHelper.cs
  • src/Core/src/Handlers/IElementHandler.cs
  • src/Core/src/Handlers/ImageButton/ImageButtonHandler.Windows.cs
  • src/Core/src/Handlers/RadioButton/RadioButtonHandler.Windows.cs
  • src/Core/src/Handlers/Slider/SliderHandler.Windows.cs
  • src/Core/src/Handlers/Stepper/StepperHandler.Windows.cs
  • src/Core/src/Handlers/Switch/SwitchHandler.Windows.cs
  • src/Core/src/Handlers/TimePicker/TimePickerHandler.Windows.cs
  • src/Core/src/Handlers/View/ViewHandler.Windows.cs
  • src/Core/src/Hosting/IMauiHandlersFactory.cs
  • src/Core/src/Hosting/Internal/MauiHandlersFactory.cs
  • src/Core/src/Platform/ElementExtensions.cs

New files (not reverted):

  • src/Core/src/Handlers/Element/IControlsMapperRemappable.cs

📱 UI Tests — Border,BoxView,Button,CheckBox,DatePicker,Editor,Entry,FlyoutPage,Frame,ImageButton,IndicatorView,Label,Layout,ListView,Navigation,Page,Picker,ProgressBar,RadioButton,RefreshView,ScrollView,SearchBar,Shape,Shell,Slider,Stepper,SwipeView,Switch,TabbedPage,TableView,TimePicker,ViewBaseTests,WebView,Window

Detected UI test categories: Border,BoxView,Button,CheckBox,DatePicker,Editor,Entry,FlyoutPage,Frame,ImageButton,IndicatorView,Label,Layout,ListView,Navigation,Page,Picker,ProgressBar,RadioButton,RefreshView,ScrollView,SearchBar,Shape,Shell,Slider,Stepper,SwipeView,Switch,TabbedPage,TableView,TimePicker,ViewBaseTests,WebView,Window

Deep UI tests — 974 passed, 4 failed across 14 categories on platform-pool agent (replaces in-process counts above). The deep UI run for 1 category (Border) hit the per-category time budget, but because the app/emulator kept crashing — each retry failed to recover from an app crash (e.g. did not recover after crash-recovery attempts) and burned its whole slice until the deadline. This is an app-stability/infrastructure issue, not a time shortfall, so raising the budget will not help (it would just spin longer). Re-run the review to try again; if it recurs, check the build-output.log + logcat in the drop-deep-uitests artifact for the app-startup crash.

🧪 UI Test Execution Results (deep, platform pool)

Category Tests Snapshot diffs
BoxView 24/25 ✓
Button 67/69 ✓
CheckBox 23/23 ✓
DatePicker 44/44 ✓
Editor 66/66 ✓
Entry 111/113 (1 ❌)
FlyoutPage 59/62 (3 ❌)
Frame 6/6 ✓
ImageButton 52/52 ✓
IndicatorView 39/39 ✓
Label 96/98 ✓
Layout 191/194 ✓
ListView 108/109 ✓
Navigation 88/89 ✓
🔍 AI analysis of failures — PR-related vs unrelated

🔍 AI-generated triage (GitHub Copilot CLI) — a heuristic judgement of whether each deep UI test failure is connected to this PR's changes. Verify before relying on it.

Mixed / uncertain: see the grouped assessment below.

  • ✗ PR-related — FlyoutPage gesture behavior on Android (~2 tests): the PR changes shared FlyoutPage and FlyoutPage.Mapper code, and the failures assert flyout gesture opening behavior that those handler/mapper-resolution changes could plausibly affect.
  • ℹ Uncertain — FlyoutPage rotation/orientation handling (~1 test): the PR touches shared FlyoutPage code, but the error is an Appium/device orientation failure ("Screen rotation cannot be changed") which can also be infrastructure or emulator state.
  • ● Unrelated — Entry theme toggle stale element (~1 test): StaleElementReferenceException while reading the cached ThemeButton after a DOM change is a known nondeterministic UI-test pattern, even though the PR touches Entry handler resolution.

Strongest signal: the FlyoutPage gesture failures line up with a changed shared control area on the Android run; the Entry stale-element failure should be treated as flake unless it repeats with a functional assertion failure.

Entry — 1 failed test
EntryClearButtonColorShouldUpdateOnThemeChange
OpenQA.Selenium.StaleElementReferenceException : Cached elements 'By.id: com.microsoft.maui.uitests:id/ThemeButton' do not exist in DOM anymore; For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#stale-element-reference-exception
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.WebDriver.InternalExecute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.WebElement.Execute(String commandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumElement.Execute(String commandName, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumElement._GetAttribute(String attributeName)
   at OpenQA.Selenium.Appium.AppiumElement.<>c__DisplayClass21_0.<GetAttribute>b__0()
   at OpenQA.Selenium.Appium.AppiumElement.Ca
...
FlyoutPage — 3 failed tests
VerifyFlyoutPage_IsGestureEnabled
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 92

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 92
ShouldKeepFlyoutLockedWhenSwitchingLandScapeToPortrait
OpenQA.Selenium.InvalidElementStateException : Screen rotation cannot be changed to ROTATION_0 after 2000ms. Is it locked programmatically?
at OpenQA.Selenium.WebDriver.UnpackAndThrowOnError(Response errorResponse, String commandToExecute)
   at OpenQA.Selenium.WebDriver.ExecuteAsync(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at OpenQA.Selenium.Appium.AppiumDriver.set_Orientation(ScreenOrientation value)
   at UITest.Appium.AppiumOrientationActions.SetOrientationPortrait(IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 43
   at UITest.Appium.AppiumOrientationActions.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUtils/src/UITest.Appium/Actions/AppiumOrientationActions.cs:line 34
   at UITest.Appium.AppiumCommandExecutor.Execute(String commandName, IDictionary`2 parameters) in /_/src/TestUti
...
VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover
Flyout did not open after multiple drag attempts
Assert.That(flyoutOpened, Is.True)
  Expected: True
  But was:  False
at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

1)    at Microsoft.Maui.TestCases.Tests.FlyoutPageFeatureTests.VerifyFlyoutPage_IsGestureEnabled_FlyoutBehaviorPopover() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/FeatureMatrix/FlyoutPageFeatureTests.cs:line 149

📎 Download drop-deep-uitests artifact (TRX + snapshot diffs)


📋 Pre-Flight — Context & Validation

Issue: #8593 / #28357 - Trimmable view handlers
PR: #29952 - [net11.0] Trimmable element handlers
Platforms Affected: All handler platforms; Android specifically for this try-fix loop
Files Changed: Broad Controls/Core handler implementation and tests; no gate files modified

Key Findings

  • PR replaces eager built-in Controls handler registration with internal [ElementHandler] attributes so unused controls/handlers can be trimmed.
  • DI exact and assignable registrations are intended to continue overriding attribute defaults.
  • Prior review issues for DI remap skipping and Windows CollectionView handler selection appear addressed in the current branch.
  • Remaining risk found for try-fix exploration: ElementExtensions.ToHandler() caches constructor-fallback by view type globally, which can bypass later factory registrations in another MauiApp.

Code Review Summary

Verdict: NEEDS_DISCUSSION
Confidence: medium
Errors: 0 | Warnings: 1 | Suggestions: 1

Key code review findings:

  • src/Core/src/Platform/ElementExtensions.cs: static handlersWithConstructors is keyed only by view type and can leak across MauiApp instances, bypassing factory-registered handlers after a previous constructor-injection fallback.
  • docs/design/HandlerResolution.md: keep documentation explicit that [ElementHandler] is internal unless the attribute is intentionally made public.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #29952 Internal [ElementHandler] attributes + DI-first factory resolution + lazy mapper remap in SetVirtualView() ⚠️ INCONCLUSIVE (Gate pre-run could not build/run) broad Core/Controls/docs/tests Original PR

🔬 Code Review — Deep Analysis

Code Review — PR #29952

Independent Assessment

What this changes: The PR migrates built-in Controls handler defaults from eager DI registrations to internal [ElementHandler] attributes discovered by MauiHandlersFactory, while preserving DI override precedence and moving Controls mapper remapping to ElementHandler.SetVirtualView().
Inferred motivation: The change is intended to make unused Controls and handlers trimmable by removing the default registration table that rooted all handlers.

Reconciliation with PR Narrative

Author claims: Built-in controls primarily use [ElementHandler]; DI exact/assignable registrations override attributes; Android Material3 and Windows CollectionView use runtime-selecting attribute subclasses; Controls mapper remaps run lazily on handler attach.
Agreement/disagreement: The implementation matches those claims at the broad design level. The remaining independently identified risk is in ElementExtensions.ToHandler(): the static view-type cache for constructor-injection fallback can leak behavior across MauiApp instances and bypass later factory registrations.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Controls remaps skipped for DI-registered handler overrides kubaflo inline review ✅ Fixed Current ElementHandler.SetVirtualView() invokes IControlsMapperRemappable.RemapForControls() before mapper updates, and DiRegisteredHandlerOverrideStillRunsControlsMapperRemapOnAttach covers DI override attach.
Windows CollectionView no longer honors RuntimeFeature.IsWindowsCollectionView2HandlerEnabled PureWeen inline review ✅ Fixed Current CollectionView.cs has a Windows-only CollectionViewHandlerAttribute that returns Handler2 or legacy handler based on the runtime feature switch.
Tizen cell renderer guards referenced missing renderers kubaflo inline review ✅ Fixed Current PR narrative and current cell guards exclude Tizen for those compatibility renderers.
Public docs implied external libraries can use internal [ElementHandler] Copilot inline review ⚠️ Needs documentation scrutiny Attribute remains internal; docs/design/HandlerResolution.md is modified in this PR and should be kept explicit that this is an internal optimization unless made public.

Blast Radius Assessment

  • Runs for all instances: Yes. Handler resolution and ToHandler() are central paths for all MAUI elements.
  • Startup impact: Medium. Default handler registrations are no longer eager, but handler resolution now uses reflection and lazy remapping on first attach.
  • Static/shared state: Yes. ElementExtensions introduced a static cache keyed only by view type; Controls remap gates are per-type static fields.

CI Status

  • Required-check result: undetermined
  • Classification: tool-unavailable / gate already inconclusive
  • Action taken: GitHub CLI is unauthenticated in this environment; user-provided gate result is inconclusive and must not be treated as PR failure.

Findings

⚠️ Warning — Static constructor-fallback cache can bypass later factory registrations

src/Core/src/Platform/ElementExtensions.cs caches view types in handlersWithConstructors after one ToHandler() path needs constructor injection. Because the cache is static and keyed only by view type, a later MauiApp that registers the same view type via AddHandler<TView>(_ => customHandler) can skip GetHandler() and go directly to GetHandlerType() + ActivatorUtilities; factory registrations have no ImplementationType, so GetHandlerType() returns null and the registered handler is bypassed. This is especially relevant to Android/context-constructed handlers because the fallback path exists to support constructor/context activation.

Failure-Mode Probing

  • Factory registration after prior constructor fallback: Current PR can fail because handlersWithConstructors survives across app instances and bypasses GetHandler().
  • DI registered handler attach: Current PR runs Controls mapper remap in SetVirtualView(), so DI override attach still remaps before mapper updates.
  • Android Material3 handler selection: Current PR uses per-control attribute subclasses that check RuntimeFeature.IsMaterial3Enabled; targeted Android device validation was unavailable in this environment.

Verdict: NEEDS_DISCUSSION

Confidence: medium
Summary: The main PR architecture is coherent and addresses prior major review findings, but the static ToHandler() constructor fallback cache is a concrete registration-order risk. Candidate 2 below is a small alternative patch that removes the cross-app cache hazard while preserving the existing constructor-injection fallback.


🛠️ Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 maui-expert-reviewer Lazy static handler registry replacing [ElementHandler] reflection ⏭️ SKIPPED many Core/Controls files Meaningfully different but too broad/high-risk for one try-fix loop
2 maui-expert-reviewer Remove static view-type constructor fallback cache; keep registration-first GetHandler() and fallback only after constructor failure ✅ PASS (local handler tests); ⚠️ Android device blocked ElementExtensions.cs, HostBuilderHandlerTests.cs Selected as better than PR's current cache behavior
PR PR #29952 Internal [ElementHandler] attributes + DI-first resolution + lazy mapper remap ⚠️ INCONCLUSIVE (Gate pre-run could not build/run) broad Core/Controls/docs/tests Original PR

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer 1 Yes Proposed lazy static self-registration registry; exhausted as too broad for this loop.
maui-expert-reviewer 2 Yes Proposed registration-safe ToHandler() fallback by removing/reworking the static constructor cache; implemented and tested.

Exhausted: Yes for meaningfully different approaches appropriate to this loop. A full registry replacement is a separate design-level refactor; cache-key variations would be trivial variations of candidate 2.
Selected Fix: Candidate #2 — it is smaller than the PR's cache behavior, preserves the existing constructor-injection fallback, and fixes a concrete cross-MauiApp factory-registration regression covered by a new unit test.


📝 Recommended PR Title & Description

Assessment: ✏️ Recommend updating — the current description is strong overall, but it says the ToHandler() constructor-injection fallback is cached per type, which is inaccurate for the winning pr-plus-reviewer fix.

Recommended title

[net11.0] Handlers: Make built-in element handlers trimmable

Recommended description

This is a follow-up to #28357.

## Summary

Built-in controls now primarily use `[ElementHandler]` attributes instead of DI registration for handler resolution. Handler associations are visible at the type level, improving trimmability because unused controls no longer need to be rooted by the default handler-registration table.

## Key changes

### `[ElementHandler]` attribute

- New internal `ElementHandlerAttribute` on view types declaratively associates a view with its handler.
- `Inherited = false` — each concrete type that needs a different handler declares its own attribute.
- Handler resolution walks the `BaseType` chain, so derived types without their own attribute inherit the nearest ancestor's handler.
- Android Material3 controls use control-specific nested attributes so the handler type can be selected conditionally without unconditionally rooting both handler implementations.
- The same nested-attribute technique selects the handler conditionally where a runtime feature switch applies: Android Material3 controls, and `CollectionView` on Windows (`CollectionViewHandler2` vs. the legacy handler, honoring `RuntimeFeature.IsWindowsCollectionView2HandlerEnabled`).

### Handler resolution in `MauiHandlersFactory`

Both `GetHandler(Type)` and `GetHandlerType(Type)` follow the same resolution order:

1. Exact DI registration — if the concrete type is registered via `AddHandler<Button, MyButtonHandler>()`, that wins.
2. Assignable DI registration — base/interface registrations still override inherited attribute defaults.
3. `[ElementHandler]` attribute — walk the type's `BaseType` chain looking for the attribute.
4. `IContentView` fallback — if the type implements `IContentView`, return `ContentViewHandler`.

`GetHandler()` is the primary API — it returns an instantiated `IElementHandler`. `GetHandlerType()` returns only the `Type` and is used by code that needs to compare handler types without instantiating them.

Attribute lookup is cached per view type to avoid repeated reflection walks during handler resolution.

### `ElementExtensions.ToHandler()` / `SetHandler()`

These methods call `GetHandler()` directly so exact and factory DI registrations remain authoritative for the active `MauiContext`.

When an attribute-resolved handler requires constructor parameters, `ToHandler()` catches the `MissingMethodException` and falls back to `ActivatorUtilities.CreateInstance()` with DI injection. This fallback is intentionally not cached by view type, because a process-wide view-type cache can bypass factory registrations in later `MauiApp` instances.

### Controls mapper remapping

Controls-layer mapper remaps no longer rely on static constructors or `RuntimeHelpers.RunClassConstructor`.

Controls remapping stays on the Controls virtual view types, matching where the old mapper static constructors lived. `ElementHandler.SetVirtualView()` invokes an internal Controls remap hook before mapper updates; each remappable control override uses its own `Interlocked.CompareExchange` field to run once, calls `base.RemapForControls()` first, then applies its own mapper changes.

This preserves the existing base-before-derived mapper ordering without forcing arbitrary class constructors to run, without putting Controls-specific remap logic on Core handlers, and without handwritten per-control handler-attribute classes.

Non-mapper command dependency setup (`CommandProperty.DependsOn(...)`) remains in the relevant control type initialization so binding behavior is independent of handler creation.

### Cleanup

- Removed `IElementHandlerWithAndroidContext` and `ElementHandlerWithAndroidContextAttribute`.
- Removed dead `RemapForControls(this MauiAppBuilder)` and old `RemapForControls()` / `RemapIfNeeded()` patterns.
- Removed `RemappingHelper` and the static-constructor-based mapper-remap helper path.

🏁 Report — Final Recommendation

Comparative Candidate Report — PR #29952

Candidate ranking

Rank Candidate Regression result Assessment
1 pr-plus-reviewer Local handler-resolution tests passed for the applied cache fix; Android device validation remains environment-blocked Best available candidate. It keeps the PR architecture and applies the safe expert-reviewer fix for the static ToHandler() constructor-fallback cache. It still inherits the mapper-remap ordering concern, but every viable candidate does.
2 try-fix-2 PASS for focused local handler tests; Android device validation blocked Functionally equivalent to the applied pr-plus-reviewer cache fix, but ranked below because the requested PR-plus-reviewer candidate is the PR path with expert feedback applied.
3 pr Gate inconclusive, not failed The raw PR fix is broadly coherent but leaves the concrete static-cache regression in ElementExtensions.ToHandler(), where one app's constructor fallback can poison later factory registrations for the same view type.
4 try-fix-1 SKIPPED A lazy static self-registration registry is meaningfully different, but it was not implemented or tested and is too broad/high-risk for this review loop.

Cross-candidate notes

  • No candidate is ranked as a regression-test failure. The gate result is inconclusive/environment-blocked, not a failing fix.
  • Candidates that passed focused regression coverage are ranked above unverified/skipped candidates.
  • The expert reviewer found an additional mapper-order risk in the PR architecture: lazy Controls remaps can run after user mapper customizations and overwrite them. This issue is not fully solved by any STEP 5a candidate; resolving it likely needs a broader design adjustment to remap before user customizations or to preserve mapper customizations structurally.

Winning candidate

Winner: pr-plus-reviewer

pr-plus-reviewer is the strongest available candidate because it preserves the PR's trimmable handler-attribute design while applying the safe, focused fix for a real cross-MauiApp handler-registration regression. try-fix-2 reaches the same code shape for the cache issue, but pr-plus-reviewer is preferred as the direct PR path with expert-reviewer feedback applied.


🧭 Next Steps — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 2 findings

See inline comments for details.

{
lock (s_controlsMapperRemapLock)
{
remappable.RemapForControls();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] Handler Mapper and Property Patterns — Running Controls mapper remapping from SetVirtualView makes it happen after app/user mapper customizations instead of during UseMauiApp setup. Concrete scenario: an app calls EntryHandler.Mapper.AppendToMapping(nameof(IEntry.Text), ...) after UseMauiApp; the first Entry attach reaches this call, Entry.RemapForControls() then does ReplaceMapping(nameof(Text), MapText), and that replacement ignores the previously appended mapping for Entry, so the app customization is silently dropped. The Controls remaps need to run before user mappings, or preserve the previous mapping when remapping lazily.

{
handler = viewType.CreateTypeWithInjection(context);
if (handler != null)
handlersWithConstructors.Add(view.GetType());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🔍 AI-Generated Review (multi-model)

[major] DI Override / Handler Resolution — This adds the view type to the process-wide handlersWithConstructors cache after a wrapped MissingMethodException. From then on, every ToHandler() for that view type skips context.Handlers.GetHandler(viewType) and goes straight to CreateTypeWithInjection. Because the cache is not scoped to the IMauiHandlersFactory/MauiApp, a later MauiApp that registers the same view with an implementation factory (AddHandler<TView>(_ => handler)) is bypassed; GetHandlerType() returns null for factory registrations and ToHandler throws instead of using the factory override. Please avoid the static view-type cache or scope it to the handler factory/handler type and fall back through GetHandler.

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

Labels

area-architecture Issues with code structure, SDK structure, implementation details copilot platform/android platform/ios platform/macos macOS / Mac Catalyst platform/windows s/agent-changes-requested AI agent recommends changes - found a better alternative or issues s/agent-fix-pr-picked AI could not beat the PR fix - PR is the best among all candidates s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

10 participants