Skip to content

Commit

Permalink
Fixed DeferredContent parents order (#15670)
Browse files Browse the repository at this point in the history
  • Loading branch information
MrJul authored May 10, 2024
1 parent 6938183 commit 7ade4fa
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 24 deletions.
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

0 comments on commit 7ade4fa

Please sign in to comment.