Skip to content

Preserve collection moves behind a XamlChangeId opt-in - #11887

Open
Sergio Pedri (Sergio0694) wants to merge 4 commits into
mainfrom
user/sergiopedri/collection-change-marshalling
Open

Sergio Pedri (Sergio0694) wants to merge 4 commits into
mainfrom
user/sergiopedri/collection-change-marshalling

Conversation

@Sergio0694

Copy link
Copy Markdown
Member

Fixes

Fixes #1503

PR Type

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no API changes)
  • Build related changes
  • Documentation content changes
  • Other (please describe):

Description

Current Behavior

BindableObservableVectorWrapper translates NotifyCollectionChangedAction.Move into a whole-vector Reset. Moving an item can consequently rebuild unaffected ListView/GridView rows and discard their state.

New Behavior

Add the independently disabled-by-default XamlChangeId.CollectionMoveNotifications opt-in. With it enabled:

  • Adapt single-item and contiguous range moves into removals followed by insertions; same-index moves emit no vector changes.
  • Project consistent intermediate counts and indexed reads from the already-updated source, without mutating or eagerly copying/enumerating it.
  • Keep IndexOf, vector views, iterators, and ItemCollection enumeration consistent with each notification.
  • Guard reentrant writes and adapter lifetime; reconcile nested source changes or callback failures with Reset while preserving the original failure information.

The existing Move-to-Reset path and ordinary Add/Remove/Replace/Reset handling remain unchanged when the opt-in is disabled. The spec documents configuration and compatibility semantics.

Customer Impact

Apps can opt in before XAML initialization:

XamlOptionalChanges.EnableChange(XamlChangeId.CollectionMoveNotifications);

This avoids whole-list invalidation and preserves unaffected containers, bindings, selection, focus, and pending edits. Moved items still undergo removal/insertion; their own containers, selection, and focus are not guaranteed to survive.

Regression Potential

  • Low risk — isolated change, limited scope
  • Medium risk — touches shared components or public APIs
  • High risk — architectural or breaking API change

The changed event sequence and intermediate view are intentionally opt-in. Shared collection consumers, range indices, reentrancy, and virtualization are the main regression surfaces.

How Has This Been Tested?

  • I have performed a self-review of my own code
  • I have added tests to cover my changes
  • Existing tests pass locally

Product, native client, isolated collection tests, managed Enterprise.Moco, and External.Framework builds succeeded. All five isolated collection tests passed, including the four new projection tests.

Added 13 managed regressions for notification-time reads, ranges, duplicates/nulls, lazy sources, reentrancy, error recovery, ListView/GridView item state, and viewport moves, plus an independent opt-in API test.

Integration validation is pending CI. Local execution of the 13 managed and seven optional-change API cases stopped in test-harness setup before test bodies ran. These are not claimed as passing locally.

Verified discovery using the actual CI Desktop/WPF/Integration/OS filter: 13/13 managed and 7/7 API tests are selected. The new managed methods explicitly select WPF rather than inheriting their class's UAP-only metadata.

build/WinUI-GitHub-PR.yml enables full validation by default and includes the RunTests stage. Its DevTestSuite selects both Microsoft.UI.Xaml.Tests.Managed.*.dll and Microsoft.UI.Xaml.Tests.External.*.dll for WPF. The WinUI-GitHub-PR (OneBranch) check is the relevant runtime gate; the lightweight GitHub Actions PR Build jobs do not run these tests.

Screenshots (if appropriate)

No visual recording captured. Regression coverage checks notification sequences and unaffected control state directly.

Register CollectionMoveNotifications as an independent, default-off XamlChangeId for GitHub issue #1503. Document the notification contract and cover flag isolation and locking.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Project progressive single-item and range removals and insertions from the already-updated source without copying or mutating it. Route vector views and ItemCollection enumeration through the adapter, reject reentrant writes, reconcile source changes, and preserve callback failures and adapter lifetime. Cover projection ranges, bounds, and intermediate reads.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add managed regressions for single and range moves, notification-time reads and retained iterators, duplicates, lazy sources, reentrancy, and callback failure recovery. Cover unchanged containers, selection, bindings, focus, editors, and viewport moves in ListView and GridView. Register explicit per-test opt-in without changing test defaults.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Override inherited UAP-only hosting metadata on the new managed tests so the existing WPF CI job selects them. Make the reentrant-source case assert the invalidation error propagated by CollectionView while updating the removed current item. Verified actual CI discovery selects all 13 managed regressions and 7 optional-change API tests.

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

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@microsoft-github-policy-service microsoft-github-policy-service Bot added the needs-triage Issue needs to be triaged by the area owners label Sep 14, 2026
@Sergio0694
Sergio Pedri (Sergio0694) marked this pull request as ready for review September 14, 2026 23:06
@Sergio0694
Sergio Pedri (Sergio0694) requested a review from a team as a code owner September 14, 2026 23:06
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@Sergio0694

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
1 pipeline(s) were filtered out due to trigger conditions.

@codendone Mike Crider (codendone) 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.

Added a few comments. I haven't reviewed the new tests.

VERIFY_IS_TRUE(!!locked);
}

void XamlOptionalChangesTests::CollectionMoveNotificationsHasIndependentOptIn()

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.

This test has minimal utility. I suggest removing it.

}
else if (_wcsicmp(name.c_str(), L"CollectionMoveNotifications") == 0)
{
changeId = xaml_settings::XamlChangeId_CollectionMoveNotifications;

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.

EnableAllXamlOptionalChanges in APITestBase.cs should also be updated.

optionalChangesStatics->EnableChange(xaml_settings::XamlChangeId_IconNoGridOptimization, &mutated);
optionalChangesStatics->EnableChange(xaml_settings::XamlChangeId_OptimizeApplyStyles, &mutated);
optionalChangesStatics->EnableChange(xaml_settings::XamlChangeId_DefaultStyleOptimizations, &mutated);
optionalChangesStatics->EnableChange(xaml_settings::XamlChangeId_DeferContextFlyoutInit, &mutated);

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.

The new opt-in should also be added here.

{
ctl::ComPtr<IInspectable> item;
bool equal = false;
IFC_RETURN(GetAt(current, &item));

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.

It looks like this raw GetAt() call will result in the repeated CheckMoveSourceUnchanged() checks and range checks of the new GetAt() method. Should this directly call BindableVectorWrapper::GetAt() for better efficiency?


IFACEMETHODIMP BindableObservableVectorWrapper::First(_Outptr_ wfc::IIterator<IInspectable *> **value)
{
if (!m_tpINCC || !OptionalChangeState::IsCollectionMoveNotificationsEnabled())

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.

Why the !m_tpINCC check? It isn't obvious to me why this is good/needed.

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

Labels

needs-triage Issue needs to be triaged by the area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReorderThemeTransition doesn't work

2 participants