Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed DeferredContent parents order #15670

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ private static void EmitEagerParentStackProvider(

definition.TypeBuilder.AddInterfaceImplementation(interfaceType);

// IReadOnlyList<object> DirectParents => (IReadOnlyList<object>)ParentsStack;
var directParentsGetter = ImplementInterfacePropertyGetter("DirectParents");
// IReadOnlyList<object> DirectParentsStack => (IReadOnlyList<object>)ParentsStack;
var directParentsGetter = ImplementInterfacePropertyGetter("DirectParentsStack");
directParentsGetter.Generator
.LdThisFld(definition.ParentListField)
.Castclass(directParentsGetter.ReturnType)
Expand Down
12 changes: 6 additions & 6 deletions src/Markup/Avalonia.Markup.Xaml/EagerParentStackEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Avalonia.Markup.Xaml;
internal struct EagerParentStackEnumerator
{
private IAvaloniaXamlIlEagerParentStackProvider? _provider;
private IReadOnlyList<object>? _currentParents;
private IReadOnlyList<object>? _currentParentsStack;
private int _currentIndex; // only valid when _currentParents isn't null

public EagerParentStackEnumerator(IAvaloniaXamlIlEagerParentStackProvider? provider)
Expand All @@ -16,18 +16,18 @@ public EagerParentStackEnumerator(IAvaloniaXamlIlEagerParentStackProvider? provi
{
while (_provider is not null)
{
if (_currentParents is null)
if (_currentParentsStack is null)
{
_currentParents = _provider.DirectParents;
_currentIndex = _currentParents.Count;
_currentParentsStack = _provider.DirectParentsStack;
_currentIndex = _currentParentsStack.Count;
}

--_currentIndex;

if (_currentIndex >= 0)
return _currentParents[_currentIndex];
return _currentParentsStack[_currentIndex];

_currentParents = null;
_currentParentsStack = null;
_provider = _provider.ParentProvider;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,36 @@

namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
/// <summary>
/// Provides the parents for the current XAML node in a lazy way.
/// </summary>
/// <remarks>This interface is used by the XAML compiler and shouldn't be implemented in your code.</remarks>
public interface IAvaloniaXamlIlParentStackProvider
{
/// <summary>
/// Gets an enumerator iterating over the available parents in the whole hierarchy.
/// The parents are returned in normal order:
/// the first element is the most direct parent while the last element is the most distant ancestor.
/// </summary>
IEnumerable<object> Parents { get; }
}

/// <summary>
/// Provides the parents for the current XAML node in an eager way, avoiding allocations when possible.
/// </summary>
/// <remarks>This interface is used by the XAML compiler and shouldn't be implemented in your code.</remarks>
public interface IAvaloniaXamlIlEagerParentStackProvider : IAvaloniaXamlIlParentStackProvider
{
IReadOnlyList<object> DirectParents { get; }
/// <summary>
/// Gets the directly available parents (which don't include ones returned by parent providers).
/// The parents are returned in reverse order:
/// the last element is the most direct parent while the first element is the most distant ancestor.
/// </summary>
IReadOnlyList<object> DirectParentsStack { get; }

/// <summary>
/// Gets the parent <see cref="IAvaloniaXamlIlEagerParentStackProvider"/>, if available.
/// </summary>
IAvaloniaXamlIlEagerParentStackProvider? ParentProvider { get; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Fun
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{
var resourceNodes = AsResourceNodes(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
var resourceNodes = AsResourceNodesStack(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
var rootObject = provider.GetRequiredService<IRootObjectProvider>().RootObject;
var parentScope = provider.GetService<INameScope>();

Expand All @@ -43,15 +43,15 @@ public static unsafe IDeferredContent DeferredTransformationFactoryV3<T>(
/*delegate*<IServiceProvider, object>*/ IntPtr builder,
IServiceProvider provider)
{
var resourceNodes = AsResourceNodes(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
var resourceNodes = AsResourceNodesStack(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
var rootObject = provider.GetRequiredService<IRootObjectProvider>().RootObject;
var parentScope = provider.GetService<INameScope>();
var typedBuilder = (delegate*<IServiceProvider, object>)builder;

return new PointerDeferredContent<T>(resourceNodes, rootObject, parentScope, typedBuilder);
}

private static IResourceNode[] AsResourceNodes(IAvaloniaXamlIlParentStackProvider provider)
private static IResourceNode[] AsResourceNodesStack(IAvaloniaXamlIlParentStackProvider provider)
{
var buffer = s_resourceNodeBuffer ??= new List<IResourceNode>(8);
buffer.Clear();
Expand All @@ -72,6 +72,9 @@ private static IResourceNode[] AsResourceNodes(IAvaloniaXamlIlParentStackProvide
}
}

// The immediate parent should be last in the stack.
buffer.Reverse();

var lastParentStack = s_lastParentStack;

if (lastParentStack is null
Expand Down Expand Up @@ -230,9 +233,9 @@ public object IntermediateRootObject
=> RootObject;

public IEnumerable<object> Parents
=> _parentResourceNodes;
=> _parentResourceNodes.Reverse();

public IReadOnlyList<object> DirectParents
public IReadOnlyList<object> DirectParentsStack
=> _parentResourceNodes;

public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
Expand Down Expand Up @@ -418,7 +421,7 @@ public ApplicationAvaloniaXamlIlParentStackProvider(Application application)
public IEnumerable<object> Parents
=> _parents ??= new object[] { _application };

public IReadOnlyList<object> DirectParents
public IReadOnlyList<object> DirectParentsStack
=> this;

public int Count
Expand Down Expand Up @@ -462,7 +465,7 @@ private EmptyAvaloniaXamlIlParentStackProvider()
public IEnumerable<object> Parents
=> Array.Empty<object>();

public IReadOnlyList<object> DirectParents
public IReadOnlyList<object> DirectParentsStack
=> Array.Empty<object>();

public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Sty
<Button Name='button'/>
</Window>")
};

using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
Expand All @@ -351,7 +351,7 @@ public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Sty

var border = (Border)button.GetVisualChildren().Single();
var brush = (ISolidColorBrush)border.Background;

Assert.Equal(0xff506070, brush.Color.ToUInt32());
}
}
Expand Down Expand Up @@ -474,6 +474,40 @@ public void StaticResource_Is_Correctly_Chosen_From_Within_DataTemplate()
}
}

[Fact]
public void StaticResource_Is_Correctly_Chosen_For_DeferredContent()
{
using (StyledWindow())
{
var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>

<Window.Resources>
<Color x:Key='Color'>Purple</Color>
</Window.Resources>

<Border>
<Border.Resources>
<Color x:Key='Color'>Red</Color>
<SolidColorBrush x:Key='Brush' Color='{StaticResource Color}' />
</Border.Resources>
<TextBlock Foreground='{StaticResource Brush}' />
</Border>

</Window>");

window.Show();

var textBlock = window.GetVisualDescendants().OfType<TextBlock>().Single();

Assert.NotNull(textBlock);
var brush = Assert.IsAssignableFrom<ISolidColorBrush>(textBlock.Foreground);
Assert.Equal(Colors.Red, brush.Color);
}
}

[Fact]
public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed()
{
Expand Down Expand Up @@ -518,7 +552,7 @@ public void Automatically_Converts_Color_To_SolidColorBrush()
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUInt32());
}

[Fact]
public void Automatically_Converts_Color_To_SolidColorBrush_From_Setter()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;

namespace Avalonia.Markup.Xaml.UnitTests.Xaml;

public class ParentStackProviderTests : XamlTestBase
{
[Fact]
public void Parents_Are_Correct_For_Deferred_Content()
{
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);

var capturedParents = new CapturedParents();
AvaloniaLocator.CurrentMutable.BindToSelf(capturedParents);

var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>

<Window.Resources>
<SolidColorBrush x:Key='Brush' Color='{local:CapturingParentsMarkupExtension}' />
</Window.Resources>

<TextBlock Foreground='{StaticResource Brush}' />

</Window>");

window.Show();

VerifyParents(capturedParents.LazyParents);
VerifyParents(capturedParents.EagerParents);

static void VerifyParents(object[]? parents)
{
Assert.NotNull(parents);
Assert.NotEmpty(parents);
Assert.Collection(
parents,
o => Assert.IsType<SolidColorBrush>(o),
o => Assert.IsType<Window>(o),
o => Assert.IsType<UnitTestApplication>(o));
}
}
}

public class CapturedParents
{
public object[]? LazyParents { get; set; }

public object[]? EagerParents { get; set; }
}

public class CapturingParentsMarkupExtension
{
public object ProvideValue(IServiceProvider serviceProvider)
{
var parentsProvider = serviceProvider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>();
var eagerParentsProvider = Assert.IsAssignableFrom<IAvaloniaXamlIlEagerParentStackProvider>(parentsProvider);

var capturedParents = AvaloniaLocator.Current.GetRequiredService<CapturedParents>();
capturedParents.LazyParents = parentsProvider.Parents.ToArray();
capturedParents.EagerParents = EnumerateEagerParents(eagerParentsProvider);

return Colors.Blue;
}

private static object[] EnumerateEagerParents(IAvaloniaXamlIlEagerParentStackProvider provider)
{
var parents = new List<object>();

var enumerator = new EagerParentStackEnumerator(provider);
while (enumerator.TryGetNext() is { } parent)
parents.Add(parent);

return parents.ToArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ public object GetService(Type serviceType)
}

public Uri BaseUri { get; set; }
public List<object> Parents { get; set; } = new List<object> { new ContentControl() };
IEnumerable<object> IAvaloniaXamlIlParentStackProvider.Parents => Parents;
public IReadOnlyList<object> DirectParents => Parents;
public IAvaloniaXamlIlEagerParentStackProvider ParentProvider => null;
public List<object> ParentsStack { get; set; } = [new ContentControl()];
IEnumerable<object> IAvaloniaXamlIlParentStackProvider.Parents => ParentsStack.AsEnumerable().Reverse();
IReadOnlyList<object> IAvaloniaXamlIlEagerParentStackProvider.DirectParentsStack => ParentsStack;
IAvaloniaXamlIlEagerParentStackProvider IAvaloniaXamlIlEagerParentStackProvider.ParentProvider => null;
}
2 changes: 1 addition & 1 deletion tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ public void Runtime_Loader_Should_Pass_Parents_From_ServiceProvider()
{
var sp = new TestServiceProvider
{
Parents = new List<object>
ParentsStack = new List<object>
{
new UserControl { Resources = { ["Resource1"] = new SolidColorBrush(Colors.Blue) } }
}
Expand Down
Loading