Browse Source

Fixed DeferredContent parents order (#15670)

release/11.1.0-rc1
Julien Lebosquain 2 years ago
committed by Steven Kirk
parent
commit
d7f928826b
  1. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  2. 12
      src/Markup/Avalonia.Markup.Xaml/EagerParentStackEnumerator.cs
  3. 23
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlParentStackProvider.cs
  4. 17
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  5. 40
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  6. 86
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ParentStackProviderTests.cs
  7. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
  8. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -110,8 +110,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
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)

12
src/Markup/Avalonia.Markup.Xaml/EagerParentStackEnumerator.cs

@ -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)
@ -16,18 +16,18 @@ internal struct EagerParentStackEnumerator
{
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;
}

23
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/IAvaloniaXamlIlParentStackProvider.cs

@ -2,15 +2,36 @@ using System.Collections.Generic;
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; }
}
}

17
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -30,7 +30,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
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>();
@ -43,7 +43,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
/*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;
@ -51,7 +51,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
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();
@ -72,6 +72,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
}
}
// The immediate parent should be last in the stack.
buffer.Reverse();
var lastParentStack = s_lastParentStack;
if (lastParentStack is null
@ -230,9 +233,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
=> RootObject;
public IEnumerable<object> Parents
=> _parentResourceNodes;
=> _parentResourceNodes.Reverse();
public IReadOnlyList<object> DirectParents
public IReadOnlyList<object> DirectParentsStack
=> _parentResourceNodes;
public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider
@ -418,7 +421,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public IEnumerable<object> Parents
=> _parents ??= new object[] { _application };
public IReadOnlyList<object> DirectParents
public IReadOnlyList<object> DirectParentsStack
=> this;
public int Count
@ -462,7 +465,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
public IEnumerable<object> Parents
=> Array.Empty<object>();
public IReadOnlyList<object> DirectParents
public IReadOnlyList<object> DirectParentsStack
=> Array.Empty<object>();
public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider

40
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs

@ -340,7 +340,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<Button Name='button'/>
</Window>")
};
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
@ -351,7 +351,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var border = (Border)button.GetVisualChildren().Single();
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUInt32());
}
}
@ -474,6 +474,40 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[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()
{
@ -518,7 +552,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
var brush = (ISolidColorBrush)border.Background;
Assert.Equal(0xff506070, brush.Color.ToUInt32());
}
[Fact]
public void Automatically_Converts_Color_To_SolidColorBrush_From_Setter()
{

86
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ParentStackProviderTests.cs

@ -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();
}
}

8
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs

@ -346,8 +346,8 @@ public class TestServiceProvider :
}
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
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -309,7 +309,7 @@ namespace Avalonia.Markup.Xaml.UnitTests
{
var sp = new TestServiceProvider
{
Parents = new List<object>
ParentsStack = new List<object>
{
new UserControl { Resources = { ["Resource1"] = new SolidColorBrush(Colors.Blue) } }
}

Loading…
Cancel
Save