Skip to content

refactor!: Remove legacy FeatureConfiguration flags (7.0)#23666

Merged
MartinZikmund merged 13 commits into
feature/breakingchangesfrom
dev/xygu/20260706/remove-legacy-feature-flags
Jul 7, 2026
Merged

refactor!: Remove legacy FeatureConfiguration flags (7.0)#23666
MartinZikmund merged 13 commits into
feature/breakingchangesfrom
dev/xygu/20260706/remove-legacy-feature-flags

Conversation

@Xiaoy312

@Xiaoy312 Xiaoy312 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

GitHub Issue: unoplatform/uno-private#2053 — https://github.com/unoplatform/uno-private/issues/2053 (internal; cross-repo, not auto-closed)

PR Type:

💬 Other — Breaking change: removal of legacy FeatureConfiguration public API. Source-breaking, behavior-preserving on shipping (Skia) targets. Targets feature/breakingchanges (7.0).

What changed? 🚀

Removes 14 legacy FeatureConfiguration flags that were permanently pinned to a single value on all shipping (Skia) targets and never assigned by the framework itself (only by SamplesApp/tests). For the 7.0 breaking major these are hard-removed (no [Obsolete] shim), and the live code path is inlined. This shrinks the public config surface with no runtime behavior change on shipping targets.

One refactor(scope)! commit per flag (BC33's two Invalidate*Path flags share one commit since they touch the same subsystem). Each commit builds green on its own and carries its own PackageDiffIgnore.xml entry, <see cref> fixes, and test/sample/doc cleanup.

# Flag removed Live path inlined
1 Binding.IgnoreINPCSameReferences (BC35) INPC same-reference path always active
2 UIElement.UseLegacyHitTest (BC18) IsViewHit() => false
3 UIElement.ShowClippingBounds (BC46) orphaned — pure delete
4 Timeline.DefaultsStartingValueFromAnimatedValue (BC06) starting value from GetValueCore()
5 VisualState.ApplySettersBeforeTransition (BC69) setters applied unconditionally after transition
6 Control.UseLegacyLazyApplyTemplate template applied eagerly
7 Control.UseLegacyTemplateSelectorOverload modern selector overload
8 ManipulationRoutedEventArgs.IsAbsolutePositionEnabled relative-transform form
9 UIElement.DisablePointersSpecificEventPrevention fall-through (no early-out)
10 FrameworkElement.UseInvalidateMeasurePath + UseInvalidateArrangePath (BC33) dirty-path always on¹
11 Control.UseDeferredOnApplyTemplate platform #if split²
12 Popup.EnableLightDismissByDefault IsLightDismissEnabled default pinned false
13 TextBox.UseOverlayOnSkia _isSkiaTextBox inlined true³

¹ BC33: only the flag term is dropped from the conditions — the else fallback branches remain reachable via the still-live per-element API FrameworkElementHelper.SetUseMeasurePathDisabled / SetUseArrangePathDisabled, which is not in this issue's scope.

² UseDeferredOnApplyTemplate is not a plain inline: its consumer sits under #if !UNO_HAS_ENHANCED_LIFECYCLE (defined for Skia + WASM), so on Skia/WASM the flag was never read. The removal replaces the flag test with #if __ANDROID__ || __APPLE_UIKIT__ (immediate) / #else (deferred) to preserve native vs. reference behavior exactly.

³ UseOverlayOnSkia: _isSkiaTextBox is inlined as the constant true it had become; the always-taken branches are unwrapped and the unreachable !_isSkiaTextBox overlay branches removed. The IOverlayTextBoxViewExtension layer is intentionally kept — still required for the invisible native text view (IME) on WASM-Skia / UIKit-Skia, gated independently of the removed flag.

Deferred to epic #2049 (drop native targets)

  • Style.UseUWPDefaultStyles family (UseUWPDefaultStyles, UseUWPDefaultStylesOverride, ConfigureNativeFrameNavigation, SetUWPDefaultStylesOverride<T>) — during implementation this proved to be native-styling subsystem removal (public StyleHelper deletion, RegisterDefaultStyleForType signature change, XAML-generator edits + Generic.Native.xaml deletion, runtime-test cascade), not a flag inline. It belongs with the native drop, so this PR leaves it untouched.

Out of scope (behavior flips — routed to Phase 5, not this PR)

  • UseLegacyContentAlignment (BC45) and UseLegacyPrimaryLanguageOverride (BC50) — these change runtime defaults and need fails-before/passes-after tests.

Tests / docs

  • Deleted legacy-asserting tests that validate the removed behavior (VSM compat-order test, the UseLegacyLazyApplyTemplate tests in Given_Control.cs, the overlay-only When_FeatureConfiguration_Changes); removed flag save/restore no-ops; de-parameterized the overlay case out of the AutoSuggestBox test.
  • Removed the corresponding flag assignments from SamplesApp perf samples and App.xaml.cs.
  • doc/articles/feature-flags.md: removed the Light Dismiss Default section.
  • build/PackageDiffIgnore.xml: added an ignore entry for every removed public static member (and the removed empty holder nested types) under the 6.6 baseline set, so the API-diff gate stays green.

Verification

  • Compile — green (0 warnings / 0 errors): each commit built Uno.UI.Skia (net10.0); flags with #if !UNO_HAS_ENHANCED_LIFECYCLE consumers also built the Reference flavor; BC33 + UseOverlayOnSkia additionally built Uno.UI.RuntimeTests.Skia and SamplesApp.Skia.Generic.
  • Runtime: behavior-preserving by construction (inlining the already-live branch); existing suites should pass after the legacy-test deletions. Full runtime/unit suite execution left to CI.
  • Package diff: PackageDiffIgnore.xml entries clear the API-diff gate on CI (not reproducible locally).

PR Checklist ✅

  • 🧪 Behavior-preserving removal — no new tests; legacy-asserting tests deleted, existing suites cover the inlined paths
  • 📚 Docs updated (feature-flags.md) where a removed flag was documented
  • 🖼️ Validated PR Screenshots Compare Test Run results
  • ❗ Contains NO breaking changes

Breaking change — impact & migration

This PR removes public FeatureConfiguration flags (7.0 breaking major). Impact: code that reads or assigns any of the 14 flags above will no longer compile. Migration: delete those assignments — every flag was pinned to its non-legacy value on shipping targets, so removing the assignment matches current runtime behavior. Covered by the 7.0 migration notes.

Xiaoy312 and others added 13 commits July 6, 2026 12:45
The legacy Binding.IgnoreINPCSameReferences flag defaulted to false on
all targets and was never assigned by the framework, so the binding
engine always took the correct INPC path. Delete the flag (and its now
empty FeatureConfiguration.Binding holder) and inline the always-false
branch at the three consumer sites in BindingPath.BindingItem.cs.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The legacy FrameworkElement.UseLegacyHitTest flag defaulted to false on
all targets and was never assigned by the framework, so IsViewHit()
always returned false (the WinUI-aligned behavior). Delete the flag and
collapse FrameworkElement.IsViewHit() to `=> false`.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UIElement.ShowClippingBounds was an orphaned diagnostic flag with zero
consumers anywhere in the tree (the iOS-only visualization it once gated
is gone). Delete the flag; setting it was already a no-op.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag was already get-only (compile-time constant true), so the
animation engine always defaulted the starting value from the animated
value via GetValueCore(). Delete the flag (and its now-empty
FeatureConfiguration.Timeline holder), inline GetValueCore(), and drop
the dead private GetNonAnimatedValue wrapper whose only caller was the
removed legacy branch. The protected Timeline.GetNonAnimatedValue base
method is kept (subclass-facing surface).

Source-breaking for consumers reading the flag; no runtime behaviour
change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework, so VisualStateGroup always applied setters at the WinUI-correct
time (after the transition). Delete the flag (and its now-empty
FeatureConfiguration.VisualState holder), remove the dead early-apply
branch, and make the post-transition ApplyTargetStateSetters() call
unconditional.

The compatibility-mode runtime test that flipped the flag to true and
asserted the legacy ordering is deleted (it exercised removed behaviour).

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework, so SetUpdateControlTemplate() always updated the template
eagerly (the `!UseLegacyLazyApplyTemplate` term was permanently true).
Delete the flag and collapse SetUpdateControlTemplate to unconditionally
call UpdateTemplate()/InvalidateMeasure(). Its `forceUpdate` parameter
only mattered in the removed legacy path, so it is dropped and the one
caller updated.

The two Given_Control unit tests existed solely to exercise the legacy
lazy sequence (asserting TemplatedRoot stays null until ApplyTemplate);
they assert removed behaviour, so the file is deleted.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework, so DataTemplateHelper always fell back to the two-arg
SelectTemplate(data, container) overload (WinUI-aligned). Delete the flag
(and its now-empty FeatureConfiguration.DataTemplateSelector holder) and
drop the flag term from the fallback condition.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework, so the Position of ManipulationStarted/Delta/Completed routed
event args was always container-relative (WinUI-aligned). Delete the flag
(and its now-empty FeatureConfiguration.ManipulationRoutedEventArgs
holder) and inline the container-relative transform at the three call
sites.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework, so PrepareManagedGestureEventBubbling always fell through to
the specific-event prevention logic (the correct behaviour). Delete the
flag and the dead CompleteGesture early-out that only ran when the flag
was set.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both flags defaulted to true on all targets and were never assigned by
the framework, so the measure/arrange dirty-path was always used
globally. Delete the flags and drop the flag term from the layout
conditions.

The legacy full-propagation `else` branches are intentionally kept: they
remain reachable per-element through the still-supported
FrameworkElementHelper.SetUseMeasurePathDisabled/SetUseArrangePathDisabled
API (IsMeasureDirtyPathDisabled / IsArrangeDirtyPathDisabled), which is
out of scope for this flag removal.

Also rewrites the four <see cref> doc-comments in FrameworkElementHelper
that referenced the removed flags (would otherwise be dangling / CS1574),
and drops the obsolete global-flag toggles from the two UIElement layout
samples (their per-element dirty-path demo and perf test are kept).

Source-breaking for consumers reading/assigning the flags; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag's sole consumer sits under #if !UNO_HAS_ENHANCED_LIFECYCLE, so it
is never read on Skia or WASM (removal is a true no-op there). Its default
was platform-conditional (false on native Android/Apple, true otherwise),
which made the observable behaviour a compile-time property, not a
runtime one:
- native Android/Apple: OnApplyTemplate applied immediately;
- reference / unit-test flavor: deferred while !IsLoaded.

Replace the runtime flag test with that same #if __ANDROID__ ||
__APPLE_UIKIT__ split so both behaviours are preserved exactly, and delete
the flag.

Verified: Uno.UI.Skia (compiled out) and Uno.UI.UnitTests / reference
(#else deferred path) build clean. The native #if branch is the verbatim
prior else-body. No runtime behaviour change. Package-diff entries added.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework, so Popup.IsLightDismissEnabled already defaulted to false
(WinUI-aligned). Delete the flag, pin the Popup default value to false,
and remove the dead AutoSuggestBox workaround that only ran when the flag
was set. Drops the corresponding feature-flags.md section.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The flag defaulted to false on all targets and was never assigned by the
framework (SamplesApp set it to false explicitly), so the Skia-based
TextBox (_isSkiaTextBox) was always used. Delete the flag, then inline
_isSkiaTextBox as the constant true it had become: fold every guard,
unwrap the always-taken branches, and delete the unreachable
!_isSkiaTextBox branches in TextBox(.skia/.pointers.skia).cs and
TextBoxView.skia.cs. In the shared TextBox.cs the #if __SKIA__ guards
turn into inverted #if !__SKIA__ guards where the folded branch runs only
on native, so native (WASM/Android/UIKit) behaviour is unchanged;
OnKeyDownNonSkia, now unreachable on Skia, moves under #if !__SKIA__.

The IOverlayTextBoxViewExtension layer is intentionally kept: it is still
required for the invisible native text view (IME) on WASM-Skia and
UIKit-Skia, gated by _useInvisibleNativeTextView independently of the
removed flag. Tearing that extension layer down is native-overlay work
that belongs with epic #2049's native drop.

Tests: removed the flag save/restore no-ops; deleted the overlay-only
When_FeatureConfiguration_Changes test; dropped the overlay case from the
parameterized AutoSuggestBox test; simplified the Skia selection-start
assertions to the skia-textbox value.

Source-breaking for consumers reading/assigning the flag; no runtime
behaviour change. Package-diff entries added for the removed public API.

Part of #2053 (remove legacy feature flags, 7.0 breaking changes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added area/skia ✏️ Categorizes an issue or PR as relevant to Skia area/build Categorizes an issue or PR as relevant to build infrastructure area/automation Categorizes an issue or PR as relevant to project automation kind/documentation labels Jul 6, 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-23666/wasm-skia-net9/index.html

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 220954 did not find any UI Test snapshots differences.

@MartinZikmund MartinZikmund merged commit 3c436b2 into feature/breakingchanges Jul 7, 2026
69 checks passed
@MartinZikmund MartinZikmund deleted the dev/xygu/20260706/remove-legacy-feature-flags branch July 7, 2026 06:56
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 area/build Categorizes an issue or PR as relevant to build infrastructure area/skia ✏️ Categorizes an issue or PR as relevant to Skia kind/documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants