Skip to content

Commit 7ade4fa

Browse files
authored
Fixed DeferredContent parents order (#15670)
1 parent 6938183 commit 7ade4fa

File tree

8 files changed

+168
-24
lines changed

8 files changed

+168
-24
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ private static void EmitEagerParentStackProvider(
110110

111111
definition.TypeBuilder.AddInterfaceImplementation(interfaceType);
112112

113-
// IReadOnlyList<object> DirectParents => (IReadOnlyList<object>)ParentsStack;
114-
var directParentsGetter = ImplementInterfacePropertyGetter("DirectParents");
113+
// IReadOnlyList<object> DirectParentsStack => (IReadOnlyList<object>)ParentsStack;
114+
var directParentsGetter = ImplementInterfacePropertyGetter("DirectParentsStack");
115115
directParentsGetter.Generator
116116
.LdThisFld(definition.ParentListField)
117117
.Castclass(directParentsGetter.ReturnType)

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Avalonia.Markup.Xaml;
66
internal struct EagerParentStackEnumerator
77
{
88
private IAvaloniaXamlIlEagerParentStackProvider? _provider;
9-
private IReadOnlyList<object>? _currentParents;
9+
private IReadOnlyList<object>? _currentParentsStack;
1010
private int _currentIndex; // only valid when _currentParents isn't null
1111

1212
public EagerParentStackEnumerator(IAvaloniaXamlIlEagerParentStackProvider? provider)
@@ -16,18 +16,18 @@ public EagerParentStackEnumerator(IAvaloniaXamlIlEagerParentStackProvider? provi
1616
{
1717
while (_provider is not null)
1818
{
19-
if (_currentParents is null)
19+
if (_currentParentsStack is null)
2020
{
21-
_currentParents = _provider.DirectParents;
22-
_currentIndex = _currentParents.Count;
21+
_currentParentsStack = _provider.DirectParentsStack;
22+
_currentIndex = _currentParentsStack.Count;
2323
}
2424

2525
--_currentIndex;
2626

2727
if (_currentIndex >= 0)
28-
return _currentParents[_currentIndex];
28+
return _currentParentsStack[_currentIndex];
2929

30-
_currentParents = null;
30+
_currentParentsStack = null;
3131
_provider = _provider.ParentProvider;
3232
}
3333

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,36 @@
22

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

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

32+
/// <summary>
33+
/// Gets the parent <see cref="IAvaloniaXamlIlEagerParentStackProvider"/>, if available.
34+
/// </summary>
1435
IAvaloniaXamlIlEagerParentStackProvider? ParentProvider { get; }
1536
}
1637
}

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Fun
3030
public static Func<IServiceProvider, object> DeferredTransformationFactoryV2<T>(Func<IServiceProvider, object> builder,
3131
IServiceProvider provider)
3232
{
33-
var resourceNodes = AsResourceNodes(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
33+
var resourceNodes = AsResourceNodesStack(provider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>());
3434
var rootObject = provider.GetRequiredService<IRootObjectProvider>().RootObject;
3535
var parentScope = provider.GetService<INameScope>();
3636

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

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

54-
private static IResourceNode[] AsResourceNodes(IAvaloniaXamlIlParentStackProvider provider)
54+
private static IResourceNode[] AsResourceNodesStack(IAvaloniaXamlIlParentStackProvider provider)
5555
{
5656
var buffer = s_resourceNodeBuffer ??= new List<IResourceNode>(8);
5757
buffer.Clear();
@@ -72,6 +72,9 @@ private static IResourceNode[] AsResourceNodes(IAvaloniaXamlIlParentStackProvide
7272
}
7373
}
7474

75+
// The immediate parent should be last in the stack.
76+
buffer.Reverse();
77+
7578
var lastParentStack = s_lastParentStack;
7679

7780
if (lastParentStack is null
@@ -230,9 +233,9 @@ public object IntermediateRootObject
230233
=> RootObject;
231234

232235
public IEnumerable<object> Parents
233-
=> _parentResourceNodes;
236+
=> _parentResourceNodes.Reverse();
234237

235-
public IReadOnlyList<object> DirectParents
238+
public IReadOnlyList<object> DirectParentsStack
236239
=> _parentResourceNodes;
237240

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

421-
public IReadOnlyList<object> DirectParents
424+
public IReadOnlyList<object> DirectParentsStack
422425
=> this;
423426

424427
public int Count
@@ -462,7 +465,7 @@ private EmptyAvaloniaXamlIlParentStackProvider()
462465
public IEnumerable<object> Parents
463466
=> Array.Empty<object>();
464467

465-
public IReadOnlyList<object> DirectParents
468+
public IReadOnlyList<object> DirectParentsStack
466469
=> Array.Empty<object>();
467470

468471
public IAvaloniaXamlIlEagerParentStackProvider? ParentProvider

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Sty
340340
<Button Name='button'/>
341341
</Window>")
342342
};
343-
343+
344344
using (StyledWindow())
345345
{
346346
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
@@ -351,7 +351,7 @@ public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Sty
351351

352352
var border = (Border)button.GetVisualChildren().Single();
353353
var brush = (ISolidColorBrush)border.Background;
354-
354+
355355
Assert.Equal(0xff506070, brush.Color.ToUInt32());
356356
}
357357
}
@@ -474,6 +474,40 @@ public void StaticResource_Is_Correctly_Chosen_From_Within_DataTemplate()
474474
}
475475
}
476476

477+
[Fact]
478+
public void StaticResource_Is_Correctly_Chosen_For_DeferredContent()
479+
{
480+
using (StyledWindow())
481+
{
482+
var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
483+
<Window xmlns='https://github.com/avaloniaui'
484+
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
485+
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'>
486+
487+
<Window.Resources>
488+
<Color x:Key='Color'>Purple</Color>
489+
</Window.Resources>
490+
491+
<Border>
492+
<Border.Resources>
493+
<Color x:Key='Color'>Red</Color>
494+
<SolidColorBrush x:Key='Brush' Color='{StaticResource Color}' />
495+
</Border.Resources>
496+
<TextBlock Foreground='{StaticResource Brush}' />
497+
</Border>
498+
499+
</Window>");
500+
501+
window.Show();
502+
503+
var textBlock = window.GetVisualDescendants().OfType<TextBlock>().Single();
504+
505+
Assert.NotNull(textBlock);
506+
var brush = Assert.IsAssignableFrom<ISolidColorBrush>(textBlock.Foreground);
507+
Assert.Equal(Colors.Red, brush.Color);
508+
}
509+
}
510+
477511
[Fact]
478512
public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed()
479513
{
@@ -518,7 +552,7 @@ public void Automatically_Converts_Color_To_SolidColorBrush()
518552
var brush = (ISolidColorBrush)border.Background;
519553
Assert.Equal(0xff506070, brush.Color.ToUInt32());
520554
}
521-
555+
522556
[Fact]
523557
public void Automatically_Converts_Color_To_SolidColorBrush_From_Setter()
524558
{
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#nullable enable
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using Avalonia.Controls;
7+
using Avalonia.Markup.Xaml.XamlIl.Runtime;
8+
using Avalonia.Media;
9+
using Avalonia.UnitTests;
10+
using Xunit;
11+
12+
namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
13+
14+
public class ParentStackProviderTests : XamlTestBase
15+
{
16+
[Fact]
17+
public void Parents_Are_Correct_For_Deferred_Content()
18+
{
19+
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
20+
21+
var capturedParents = new CapturedParents();
22+
AvaloniaLocator.CurrentMutable.BindToSelf(capturedParents);
23+
24+
var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
25+
<Window xmlns='https://github.com/avaloniaui'
26+
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
27+
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
28+
29+
<Window.Resources>
30+
<SolidColorBrush x:Key='Brush' Color='{local:CapturingParentsMarkupExtension}' />
31+
</Window.Resources>
32+
33+
<TextBlock Foreground='{StaticResource Brush}' />
34+
35+
</Window>");
36+
37+
window.Show();
38+
39+
VerifyParents(capturedParents.LazyParents);
40+
VerifyParents(capturedParents.EagerParents);
41+
42+
static void VerifyParents(object[]? parents)
43+
{
44+
Assert.NotNull(parents);
45+
Assert.NotEmpty(parents);
46+
Assert.Collection(
47+
parents,
48+
o => Assert.IsType<SolidColorBrush>(o),
49+
o => Assert.IsType<Window>(o),
50+
o => Assert.IsType<UnitTestApplication>(o));
51+
}
52+
}
53+
}
54+
55+
public class CapturedParents
56+
{
57+
public object[]? LazyParents { get; set; }
58+
59+
public object[]? EagerParents { get; set; }
60+
}
61+
62+
public class CapturingParentsMarkupExtension
63+
{
64+
public object ProvideValue(IServiceProvider serviceProvider)
65+
{
66+
var parentsProvider = serviceProvider.GetRequiredService<IAvaloniaXamlIlParentStackProvider>();
67+
var eagerParentsProvider = Assert.IsAssignableFrom<IAvaloniaXamlIlEagerParentStackProvider>(parentsProvider);
68+
69+
var capturedParents = AvaloniaLocator.Current.GetRequiredService<CapturedParents>();
70+
capturedParents.LazyParents = parentsProvider.Parents.ToArray();
71+
capturedParents.EagerParents = EnumerateEagerParents(eagerParentsProvider);
72+
73+
return Colors.Blue;
74+
}
75+
76+
private static object[] EnumerateEagerParents(IAvaloniaXamlIlEagerParentStackProvider provider)
77+
{
78+
var parents = new List<object>();
79+
80+
var enumerator = new EagerParentStackEnumerator(provider);
81+
while (enumerator.TryGetNext() is { } parent)
82+
parents.Add(parent);
83+
84+
return parents.ToArray();
85+
}
86+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,8 +346,8 @@ public object GetService(Type serviceType)
346346
}
347347

348348
public Uri BaseUri { get; set; }
349-
public List<object> Parents { get; set; } = new List<object> { new ContentControl() };
350-
IEnumerable<object> IAvaloniaXamlIlParentStackProvider.Parents => Parents;
351-
public IReadOnlyList<object> DirectParents => Parents;
352-
public IAvaloniaXamlIlEagerParentStackProvider ParentProvider => null;
349+
public List<object> ParentsStack { get; set; } = [new ContentControl()];
350+
IEnumerable<object> IAvaloniaXamlIlParentStackProvider.Parents => ParentsStack.AsEnumerable().Reverse();
351+
IReadOnlyList<object> IAvaloniaXamlIlEagerParentStackProvider.DirectParentsStack => ParentsStack;
352+
IAvaloniaXamlIlEagerParentStackProvider IAvaloniaXamlIlEagerParentStackProvider.ParentProvider => null;
353353
}

tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ public void Runtime_Loader_Should_Pass_Parents_From_ServiceProvider()
309309
{
310310
var sp = new TestServiceProvider
311311
{
312-
Parents = new List<object>
312+
ParentsStack = new List<object>
313313
{
314314
new UserControl { Resources = { ["Resource1"] = new SolidColorBrush(Colors.Blue) } }
315315
}

0 commit comments

Comments
 (0)