Browse Source

Merge branch 'master' into feature/window-show-hide-with-isvisible

pull/9562/head
Dan Walmsley 3 years ago
parent
commit
7b633295e8
  1. 4
      packages/Avalonia/AvaloniaBuildTasks.targets
  2. 14
      samples/BindingDemo/App.xaml
  3. 18
      samples/ControlCatalog/Pages/ExpanderPage.xaml
  4. 6
      samples/ControlCatalog/Pages/ExpanderPage.xaml.cs
  5. 11
      src/Avalonia.Base/Platform/AssetLoader.cs
  6. 2
      src/Avalonia.Base/Rect.cs
  7. 1
      src/Avalonia.Base/Utilities/StringBuilderCache.cs
  8. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  9. 2
      src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs
  10. 22
      src/Avalonia.Build.Tasks/Extensions.cs
  11. 229
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  12. 251
      src/Avalonia.Controls/Expander.cs
  13. 2
      src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs
  14. 3
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  15. 15
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  16. 50
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
  17. 133
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  18. 51
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  19. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  20. 78
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs
  21. 173
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs
  22. 17
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs
  23. 93
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs
  24. 9
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  25. 21
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs
  26. 40
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs
  27. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  28. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  29. 23
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  30. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs
  31. 13
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs
  32. 70
      src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs
  33. 3
      src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs
  34. 3
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  35. 10
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  36. 6
      src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
  37. 6
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  38. 108
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs
  39. 44
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs
  40. 44
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/StaticResourceExtensionTests.cs
  41. 51
      tests/Avalonia.Markup.Xaml.UnitTests/StyleIncludeTests.cs
  42. 22
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  43. 22
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs
  44. 3
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml
  45. 3
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml
  46. 268
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs
  47. 39
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs
  48. 4
      tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs

4
packages/Avalonia/AvaloniaBuildTasks.targets

@ -74,7 +74,7 @@
ReportImportance="$(AvaloniaXamlReportImportance)"/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
</Target>
@ -112,7 +112,7 @@
/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileAvaloniaXaml /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:BuildProjectReferences=false"/>
</Target>

14
samples/BindingDemo/App.xaml

@ -2,8 +2,14 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BindingDemo.App">
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml"/>
</Application.Styles>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://Avalonia.Themes.Simple/Accents/BaseLight.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

18
samples/ControlCatalog/Pages/ExpanderPage.xaml

@ -52,6 +52,24 @@
</StackPanel>
</Expander>
<CheckBox IsChecked="{Binding Rounded}">Rounded</CheckBox>
<Expander x:Name="CollapsingDisabledExpander"
Header="Collapsing Disabled"
IsExpanded="True"
ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
<Expander x:Name="ExpandingDisabledExpander"
Header="Expanding Disabled"
IsExpanded="False"
ExpandDirection="Down"
CornerRadius="{Binding CornerRadius}">
<StackPanel>
<TextBlock>Expanded content</TextBlock>
</StackPanel>
</Expander>
</StackPanel>
</StackPanel>
</UserControl>

6
samples/ControlCatalog/Pages/ExpanderPage.xaml.cs

@ -10,6 +10,12 @@ namespace ControlCatalog.Pages
{
this.InitializeComponent();
DataContext = new ExpanderPageViewModel();
var CollapsingDisabledExpander = this.Get<Expander>("CollapsingDisabledExpander");
var ExpandingDisabledExpander = this.Get<Expander>("ExpandingDisabledExpander");
CollapsingDisabledExpander.Collapsing += (s, e) => { e.Handled = true; };
ExpandingDisabledExpander.Expanding += (s, e) => { e.Handled = true; };
}
private void InitializeComponent()

11
src/Avalonia.Base/Platform/AssetLoader.cs

@ -3,16 +3,22 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
#if !BUILDTASK
using Avalonia.Platform.Internal;
using Avalonia.Utilities;
#endif
namespace Avalonia.Platform
{
/// <summary>
/// Loads assets compiled into the application binary.
/// </summary>
public class AssetLoader : IAssetLoader
public class AssetLoader
#if !BUILDTASK
: IAssetLoader
#endif
{
#if !BUILDTASK
private static IAssemblyDescriptorResolver s_assemblyDescriptorResolver = new AssemblyDescriptorResolver();
private AssemblyDescriptor? _defaultResmAssembly;
@ -206,7 +212,8 @@ namespace Avalonia.Platform
return null;
}
#endif
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))

2
src/Avalonia.Base/Rect.cs

@ -243,7 +243,7 @@ namespace Avalonia
}
/// <summary>
/// Determines whether a point in in the bounds of the rectangle.
/// Determines whether a point is in the bounds of the rectangle.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>

1
src/Avalonia.Base/Utilities/StringBuilderCache.cs

@ -9,6 +9,7 @@ using System;
using System.Text;
namespace Avalonia.Utilities;
#nullable enable
// <summary>Provide a cached reusable instance of stringbuilder per thread.</summary>
internal static class StringBuilderCache

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -55,6 +55,9 @@
<Compile Include="../Avalonia.Base/Utilities/MathUtilities.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\Avalonia.Base\Platform\AssetLoader.cs">
<Link>Markup\AssetLoader.cs</Link>
</Compile>
<Compile Include="..\Avalonia.Base\Utilities\StringBuilderCache.cs" Link="Utilities\StringBuilderCache.cs" />
<Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\ArgumentListParser.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>

2
src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs

@ -5,6 +5,8 @@ namespace Avalonia.Build.Tasks
InvalidXAML = 1,
DuplicateXClass = 2,
LegacyResmScheme = 3,
TransformError = 4,
EmitError = 4,
Unknown = 9999
}

22
src/Avalonia.Build.Tasks/Extensions.cs

@ -7,15 +7,29 @@ namespace Avalonia.Build.Tasks
{
static string FormatErrorCode(BuildEngineErrorCode code) => $"AVLN:{(int)code:0000}";
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, Exception ex,
int lineNumber = 0, int linePosition = 0)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
#if DEBUG
LogError(engine, code, file, ex.ToString(), lineNumber, linePosition);
#else
LogError(engine, code, file, ex.Message, lineNumber, linePosition);
#endif
}
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
int lineNumber = 0, int linePosition = 0)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
lineNumber, linePosition, lineNumber, linePosition, message,
"", "Avalonia"));
}
public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message,
int lineNumber = 0, int linePosition = 0)
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "",
lineNumber, linePosition, lineNumber, linePosition, message,
"", "Avalonia"));
}

229
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Platform;
using Microsoft.Build.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
@ -94,7 +95,7 @@ namespace Avalonia.Build.Tasks
}
catch (Exception ex)
{
engine.LogError(BuildEngineErrorCode.Unknown, "", ex.Message);
engine.LogError(BuildEngineErrorCode.Unknown, "", ex);
return new CompileResult(false);
}
}
@ -134,6 +135,10 @@ namespace Avalonia.Build.Tasks
engine.LogMessage("Debugging cancelled.", MessageImportance.Normal);
}
}
// Some transformers might need to parse "avares://" Uri.
AssetLoader.RegisterResUriParsers();
var asm = typeSystem.TargetAssemblyDefinition;
var avares = new AvaloniaResources(asm, projectDirectory);
if (avares.Resources.Count(CheckXamlName) == 0)
@ -231,15 +236,14 @@ namespace Avalonia.Build.Tasks
});
asm.MainModule.Types.Add(typeDef);
var builder = typeSystem.CreateTypeBuilder(typeDef);
var populateMethodsToTransform = new List<(MethodDefinition populateMethod, string resourceFilePath)>();
foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x=>x.FilePath.ToLowerInvariant()))
IReadOnlyCollection<XamlDocumentResource> parsedXamlDocuments = new List<XamlDocumentResource>();
foreach (var res in group.Resources.Where(CheckXamlName).OrderBy(x => x.FilePath.ToLowerInvariant()))
{
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
try
{
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlParser.Parse(xaml);
@ -276,9 +280,9 @@ namespace Avalonia.Build.Tasks
compiler.Transform(parsed);
var populateName = classType == null ? "Populate:" + res.Name : "!XamlIlPopulate";
var buildName = classType == null ? "Build:" + res.Name : null;
var buildName = classType == null ? "Build:" + res.Name : null;
var classTypeDefinition =
classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
@ -286,11 +290,57 @@ namespace Avalonia.Build.Tasks
var populateBuilder = classTypeDefinition == null ?
builder :
typeSystem.CreateTypeBuilder(classTypeDefinition);
compiler.Compile(parsed,
contextClass,
((List<XamlDocumentResource>)parsedXamlDocuments).Add(new XamlDocumentResource(
parsed, res.Uri, res, classType,
populateBuilder,
compiler.DefinePopulateMethod(populateBuilder, parsed, populateName,
classTypeDefinition == null),
buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true),
buildName == null ? null : compiler.DefineBuildMethod(builder, parsed, buildName, true)));
}
catch (Exception e)
{
int lineNumber = 0, linePosition = 0;
if (e is XamlParseException xe)
{
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
}
engine.LogError(BuildEngineErrorCode.TransformError, res.FilePath, e, lineNumber, linePosition);
return false;
}
}
try
{
compiler.TransformGroup(parsedXamlDocuments);
}
catch (XamlDocumentParseException e)
{
engine.LogError(BuildEngineErrorCode.TransformError, e.FilePath, e, e.LineNumber, e.LinePosition);
}
catch (XamlParseException e)
{
engine.LogError(BuildEngineErrorCode.TransformError, "", e, e.LineNumber, e.LinePosition);
}
foreach (var document in parsedXamlDocuments)
{
var parsed = document.XamlDocument;
var res = (IResource)document.FileSource;
var classType = document.ClassType;
var populateBuilder = document.TypeBuilder;
try
{
var classTypeDefinition =
classType == null ? null : typeSystem.GetTypeReference(classType).Resolve();
compiler.Compile(parsed,
contextClass,
document.PopulateMethod,
document.BuildMethod,
builder.DefineSubType(compilerConfig.WellKnownTypes.Object, "NamespaceInfo:" + res.Name,
true),
(closureName, closureBaseType) =>
@ -300,23 +350,11 @@ namespace Avalonia.Build.Tasks
res.Uri, res
);
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
.Methods.First(m => m.Name == populateName);
// Include populate method and all nested methods/closures used in the populate method,
// So we can replace style/resource includes in all of them.
populateMethodsToTransform.Add((compiledPopulateMethod, res.FilePath));
populateMethodsToTransform.AddRange(compiledPopulateMethod.Body.Instructions
.Where(b => b.OpCode == OpCodes.Call || b.OpCode == OpCodes.Callvirt || b.OpCode == OpCodes.Ldftn)
.Select(b => b.Operand)
.OfType<MethodReference>()
.Where(m => compiledPopulateMethod.DeclaringType.NestedTypes.Contains(m.DeclaringType))
.Select(m => m.Resolve())
.Where(m => m.HasBody)
.Select(m => (m, res.FilePath)));
if (classTypeDefinition != null)
{
var compiledPopulateMethod = typeSystem.GetTypeReference(populateBuilder).Resolve()
.Methods.First(m => m.Name == document.PopulateMethod.Name);
var designLoaderFieldType = typeSystem
.GetType("System.Action`1")
.MakeGenericType(typeSystem.GetType("System.Object"));
@ -370,15 +408,11 @@ namespace Avalonia.Build.Tasks
if (i[c].OpCode == OpCodes.Call)
{
var op = i[c].Operand as MethodReference;
// TODO: Throw an error
// This usually happens when the same XAML resource was added twice for some weird reason
// We currently support it for dual-named default theme resources
if (op != null
&& op.Name == TrampolineName)
{
foundXamlLoader = true;
break;
throw new InvalidProgramException("Same XAML file was loaded twice." +
"Make sure there is no x:Class duplicates no files were added to the AvaloniaResource msbuild items group twice.");
}
if (op != null
&& op.Name == "Load"
@ -417,12 +451,12 @@ namespace Avalonia.Build.Tasks
}
if (buildName != null || classTypeDefinition != null)
if (document.BuildMethod != null || classTypeDefinition != null)
{
var compiledBuildMethod = buildName == null ?
var compiledBuildMethod = document.BuildMethod == null ?
null :
typeSystem.GetTypeReference(builder).Resolve()
.Methods.First(m => m.Name == buildName);
.Methods.First(m => m.Name == document.BuildMethod?.Name);
var parameterlessConstructor = compiledBuildMethod != null ?
null :
classTypeDefinition.GetConstructors().FirstOrDefault(c =>
@ -459,22 +493,12 @@ namespace Avalonia.Build.Tasks
lineNumber = xe.LineNumber;
linePosition = xe.LinePosition;
}
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", res.FilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
engine.LogError(BuildEngineErrorCode.EmitError, res.FilePath, e, lineNumber, linePosition);
return false;
}
res.Remove();
}
foreach (var (populateMethod, resourceFilePath) in populateMethodsToTransform)
{
if (!TransformXamlIncludes(engine, typeSystem, populateMethod, resourceFilePath, createRootServiceProviderMethod))
{
return false;
}
}
// Technically that's a hack, but it fixes corert incompatibility caused by deterministic builds
int dupeCounter = 1;
foreach (var grp in typeDef.NestedTypes.GroupBy(x => x.Name))
@ -551,116 +575,5 @@ namespace Avalonia.Build.Tasks
return true;
}
private static bool TransformXamlIncludes(
IBuildEngine engine, CecilTypeSystem typeSystem,
MethodDefinition populateMethod, string resourceFilePath,
MethodReference createRootServiceProviderMethod)
{
var asm = typeSystem.TargetAssemblyDefinition;
foreach (var instruction in populateMethod.Body.Instructions.ToArray())
{
const string resolveStyleIncludeName = "ResolveStyleInclude";
const string resolveResourceInclude = "ResolveResourceInclude";
if (instruction.OpCode == OpCodes.Call
&& instruction.Operand is MethodReference
{
Name: resolveStyleIncludeName or resolveResourceInclude,
DeclaringType: { Name: "XamlIlRuntimeHelpers" }
})
{
int lineNumber = 0, linePosition = 0;
bool instructionsModified = false;
try
{
var assetSource = (string)instruction.Previous.Previous.Previous.Operand;
lineNumber = GetConstValue(instruction.Previous.Previous);
linePosition = GetConstValue(instruction.Previous);
var index = populateMethod.Body.Instructions.IndexOf(instruction);
assetSource = assetSource.Replace("avares://", "");
var assemblyNameSeparator = assetSource.IndexOf('/');
var fileNameSeparator = assetSource.LastIndexOf('/');
if (assemblyNameSeparator < 0 || fileNameSeparator < 0)
{
throw new InvalidProgramException(
$"Invalid asset source \"{assetSource}\". It must contain assembly name and relative path.");
}
var assetAssemblyName = assetSource.Substring(0, assemblyNameSeparator);
var assetAssembly = typeSystem.FindAssembly(assetAssemblyName)
?? throw new InvalidProgramException(
$"Unable to resolve assembly \"{assetAssemblyName}\"");
var fileName = Path.GetFileNameWithoutExtension(assetSource.Replace('/', '.'));
if (assetAssembly.FindType(fileName) is { } type
&& type.FindConstructor() is { } ctor)
{
var ctorMethod =
asm.MainModule.ImportReference(typeSystem.GetMethodReference(ctor));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Newobj, ctorMethod);
}
else
{
var resources = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources")
?? throw new InvalidOperationException(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assetAssemblyName}\" assembly");
var relativeName = "Build:" + assetSource.Substring(assemblyNameSeparator);
var buildMethod = resources.FindMethod(m => m.Name == relativeName)
?? throw new InvalidOperationException(
$"Unable to resolve build method \"{relativeName}\" resource on the \"{assetAssemblyName}\" assembly");
var methodReference = asm.MainModule.ImportReference(typeSystem.GetMethodReference(buildMethod));
instructionsModified = true;
populateMethod.Body.Instructions[index - 3] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 2] = Instruction.Create(OpCodes.Nop);
populateMethod.Body.Instructions[index - 1] =
Instruction.Create(OpCodes.Call, createRootServiceProviderMethod);
populateMethod.Body.Instructions[index] = Instruction.Create(OpCodes.Call, methodReference);
}
}
catch (Exception e)
{
if (instructionsModified)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", "XAMLIL", resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
return false;
}
else
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", "XAMLIL",
resourceFilePath,
lineNumber, linePosition, lineNumber, linePosition,
e.Message, "", "Avalonia"));
}
}
static int GetConstValue(Instruction instruction)
{
if (instruction.OpCode is { Code : >= Code.Ldc_I4_0 and <= Code.Ldc_I4_8 })
{
return instruction.OpCode.Code - Code.Ldc_I4_0;
}
if (instruction.Operand is not null)
{
return Convert.ToInt32(instruction.Operand);
}
return 0;
}
}
}
return true;
}
}
}

251
src/Avalonia.Controls/Expander.cs

@ -1,7 +1,11 @@
using System;
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Interactivity;
using Avalonia.Threading;
namespace Avalonia.Controls
{
@ -37,12 +41,24 @@ namespace Avalonia.Controls
[PseudoClasses(":expanded", ":up", ":down", ":left", ":right")]
public class Expander : HeaderedContentControl
{
/// <summary>
/// Defines the <see cref="ContentTransition"/> property.
/// </summary>
public static readonly StyledProperty<IPageTransition?> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition?>(nameof(ContentTransition));
AvaloniaProperty.Register<Expander, IPageTransition?>(
nameof(ContentTransition));
/// <summary>
/// Defines the <see cref="ExpandDirection"/> property.
/// </summary>
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =
AvaloniaProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down);
AvaloniaProperty.Register<Expander, ExpandDirection>(
nameof(ExpandDirection),
ExpandDirection.Down);
/// <summary>
/// Defines the <see cref="IsExpanded"/> property.
/// </summary>
public static readonly DirectProperty<Expander, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<Expander, bool>(
nameof(IsExpanded),
@ -50,47 +66,206 @@ namespace Avalonia.Controls
(o, v) => o.IsExpanded = v,
defaultBindingMode: Data.BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="Collapsed"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CollapsedEvent =
RoutedEvent.Register<Expander, RoutedEventArgs>(
nameof(Collapsed),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Collapsing"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CollapsingEvent =
RoutedEvent.Register<Expander, RoutedEventArgs>(
nameof(Collapsing),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Expanded"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> ExpandedEvent =
RoutedEvent.Register<Expander, RoutedEventArgs>(
nameof(Expanded),
RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="Expanding"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> ExpandingEvent =
RoutedEvent.Register<Expander, RoutedEventArgs>(
nameof(Expanding),
RoutingStrategies.Bubble);
private bool _ignorePropertyChanged = false;
private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
static Expander()
{
IsExpandedProperty.Changed.AddClassHandler<Expander>((x, e) => x.OnIsExpandedChanged(e));
}
/// <summary>
/// Initializes a new instance of the <see cref="Expander"/> class.
/// </summary>
public Expander()
{
UpdatePseudoClasses(ExpandDirection);
UpdatePseudoClasses();
}
/// <summary>
/// Gets or sets the transition used when expanding or collapsing the content.
/// </summary>
public IPageTransition? ContentTransition
{
get => GetValue(ContentTransitionProperty);
set => SetValue(ContentTransitionProperty, value);
}
/// <summary>
/// Gets or sets the direction in which the <see cref="Expander"/> opens.
/// </summary>
public ExpandDirection ExpandDirection
{
get => GetValue(ExpandDirectionProperty);
set => SetValue(ExpandDirectionProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the <see cref="Expander"/>
/// content area is open and visible.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
PseudoClasses.Set(":expanded", value);
get => _isExpanded;
set
{
// It is important here that IsExpanded is a direct property so events can be invoked
// BEFORE the property system gets notified of updated values. This is because events
// may be canceled by external code.
if (_isExpanded != value)
{
RoutedEventArgs eventArgs;
if (value)
{
eventArgs = new RoutedEventArgs(ExpandingEvent, this);
OnExpanding(eventArgs);
}
else
{
eventArgs = new RoutedEventArgs(CollapsingEvent, this);
OnCollapsing(eventArgs);
}
if (eventArgs.Handled)
{
// If the event was externally handled (canceled) we must still notify the value has changed.
// This property changed notification will update any external code observing this property that itself may have set the new value.
// We are essentially reverted any external state change along with ignoring the IsExpanded property set.
// Remember IsExpanded is usually controlled by a ToggleButton in the control theme.
_ignorePropertyChanged = true;
RaisePropertyChanged(
IsExpandedProperty,
oldValue: value,
newValue: _isExpanded,
BindingPriority.LocalValue,
isEffectiveValue: true);
_ignorePropertyChanged = false;
}
else
{
SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
}
}
}
}
protected virtual async void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
/// <summary>
/// Occurs after the content area has closed and only the header is visible.
/// </summary>
public event EventHandler<RoutedEventArgs>? Collapsed
{
add => AddHandler(CollapsedEvent, value);
remove => RemoveHandler(CollapsedEvent, value);
}
/// <summary>
/// Occurs as the content area is closing.
/// </summary>
/// <remarks>
/// The event args <see cref="RoutedEventArgs.Handled"/> property may be set to true to cancel the event
/// and keep the control open (expanded).
/// </remarks>
public event EventHandler<RoutedEventArgs>? Collapsing
{
add => AddHandler(CollapsingEvent, value);
remove => RemoveHandler(CollapsingEvent, value);
}
/// <summary>
/// Occurs after the <see cref="Expander"/> has opened to display both its header and content.
/// </summary>
public event EventHandler<RoutedEventArgs>? Expanded
{
add => AddHandler(ExpandedEvent, value);
remove => RemoveHandler(ExpandedEvent, value);
}
/// <summary>
/// Occurs as the content area is opening.
/// </summary>
/// <remarks>
/// The event args <see cref="RoutedEventArgs.Handled"/> property may be set to true to cancel the event
/// and keep the control closed (collapsed).
/// </remarks>
public event EventHandler<RoutedEventArgs>? Expanding
{
add => AddHandler(ExpandingEvent, value);
remove => RemoveHandler(ExpandingEvent, value);
}
/// <summary>
/// Invoked just before the <see cref="Collapsed"/> event.
/// </summary>
protected virtual void OnCollapsed(RoutedEventArgs eventArgs)
{
RaiseEvent(eventArgs);
}
/// <summary>
/// Invoked just before the <see cref="Collapsing"/> event.
/// </summary>
protected virtual void OnCollapsing(RoutedEventArgs eventArgs)
{
RaiseEvent(eventArgs);
}
/// <summary>
/// Invoked just before the <see cref="Expanded"/> event.
/// </summary>
protected virtual void OnExpanded(RoutedEventArgs eventArgs)
{
RaiseEvent(eventArgs);
}
/// <summary>
/// Invoked just before the <see cref="Expanding"/> event.
/// </summary>
protected virtual void OnExpanding(RoutedEventArgs eventArgs)
{
RaiseEvent(eventArgs);
}
/// <summary>
/// Starts the content transition (if set) and invokes the <see cref="Expanded"/>
/// and <see cref="Collapsed"/> events when completed.
/// </summary>
private async void StartContentTransition()
{
if (Content != null && ContentTransition != null && Presenter is Visual visualContent)
{
bool forward = ExpandDirection == ExpandDirection.Left ||
ExpandDirection == ExpandDirection.Up;
ExpandDirection == ExpandDirection.Up;
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
@ -104,24 +279,58 @@ namespace Avalonia.Controls
await ContentTransition.Start(visualContent, null, forward, _lastTransitionCts.Token);
}
}
// Expanded/Collapsed events are invoked asynchronously to ensure other events,
// such as Click, have time to complete first.
Dispatcher.UIThread.Post(() =>
{
if (IsExpanded)
{
OnExpanded(new RoutedEventArgs(ExpandedEvent, this));
}
else
{
OnCollapsed(new RoutedEventArgs(CollapsedEvent, this));
}
});
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (_ignorePropertyChanged)
{
return;
}
if (change.Property == ExpandDirectionProperty)
{
UpdatePseudoClasses(change.GetNewValue<ExpandDirection>());
UpdatePseudoClasses();
}
else if (change.Property == IsExpandedProperty)
{
// Expanded/Collapsed will be raised once transitions are complete
StartContentTransition();
UpdatePseudoClasses();
}
}
private void UpdatePseudoClasses(ExpandDirection d)
/// <summary>
/// Updates the visual state of the control by applying latest PseudoClasses.
/// </summary>
private void UpdatePseudoClasses()
{
PseudoClasses.Set(":up", d == ExpandDirection.Up);
PseudoClasses.Set(":down", d == ExpandDirection.Down);
PseudoClasses.Set(":left", d == ExpandDirection.Left);
PseudoClasses.Set(":right", d == ExpandDirection.Right);
var expandDirection = ExpandDirection;
PseudoClasses.Set(":up", expandDirection == ExpandDirection.Up);
PseudoClasses.Set(":down", expandDirection == ExpandDirection.Down);
PseudoClasses.Set(":left", expandDirection == ExpandDirection.Left);
PseudoClasses.Set(":right", expandDirection == ExpandDirection.Right);
PseudoClasses.Set(":expanded", IsExpanded);
}
}
}

2
src/Avalonia.Controls/Flyouts/FlyoutPresenter.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
}
}
base.OnKeyDown(e);
base.OnKeyDown(e);
}
}
}

3
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -38,10 +38,9 @@ namespace Avalonia.DesignerSupport
var useCompiledBindings = localAsm?.GetCustomAttributes<AssemblyMetadataAttribute>()
.FirstOrDefault(a => a.Key == "AvaloniaUseCompiledBindingsByDefault")?.Value;
var loaded = loader.Load(stream, new RuntimeXamlLoaderConfiguration
var loaded = loader.Load(new RuntimeXamlLoaderDocument(baseUri, stream), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = localAsm,
BaseUri = baseUri,
DesignMode = true,
UseCompiledBindingsByDefault = bool.TryParse(useCompiledBindings, out var parsedValue ) && parsedValue
});

15
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -103,17 +103,18 @@
RenderTransformOrigin="50%,50%"
Stretch="None"
Stroke="{DynamicResource ExpanderChevronForeground}"
StrokeThickness="1" />
<Border.RenderTransform>
<RotateTransform />
</Border.RenderTransform>
StrokeThickness="1">
<Path.RenderTransform>
<RotateTransform />
</Path.RenderTransform>
</Path>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:checked /template/ Border#ExpandCollapseChevronBorder">
<Style Selector="^:checked /template/ Path#ExpandCollapseChevron">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
<KeyFrame Cue="100%">
@ -122,8 +123,8 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="^:not(:checked) /template/ Border#ExpandCollapseChevronBorder">
<Style Selector="^:not(:checked) /template/ Path#ExpandCollapseChevron">
<Style.Animations>
<Animation FillMode="Both" Duration="0:0:0.0625">
<KeyFrame Cue="0%">

50
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs

@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl;
#nullable enable
namespace Avalonia.Markup.Xaml
{
public static class AvaloniaRuntimeXamlLoader
@ -17,31 +18,15 @@ namespace Avalonia.Markup.Xaml
/// <param name="uri">The URI of the XAML being loaded.</param>
/// <param name="designMode">Indicates whether the XAML is being loaded in design mode.</param>
/// <returns>The loaded object.</returns>
public static object Load(string xaml, Assembly localAssembly = null, object rootInstance = null, Uri uri = null, bool designMode = false)
public static object Load(string xaml, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null, bool designMode = false)
{
Contract.Requires<ArgumentNullException>(xaml != null);
xaml = xaml ?? throw new ArgumentNullException(nameof(xaml));
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
{
return Load(stream, localAssembly, rootInstance, uri, designMode);
}
}
/// <summary>
/// Loads XAML from a string.
/// </summary>
/// <param name="xaml">The string containing the XAML.</param>
/// <param name="configuration">Xaml loader configuration.</param>
/// <returns>The loaded object.</returns>
public static object Load(string xaml, RuntimeXamlLoaderConfiguration configuration)
{
Contract.Requires<ArgumentNullException>(xaml != null);
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
{
return Load(stream, configuration);
}
}
/// <summary>
/// Loads XAML from a stream.
@ -52,19 +37,28 @@ namespace Avalonia.Markup.Xaml
/// <param name="uri">The URI of the XAML being loaded.</param>
/// <param name="designMode">Indicates whether the XAML is being loaded in design mode.</param>
/// <returns>The loaded object.</returns>
public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null,
public static object Load(Stream stream, Assembly? localAssembly = null, object? rootInstance = null, Uri? uri = null,
bool designMode = false)
=> AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode, false);
=> AvaloniaXamlIlRuntimeCompiler.Load(new RuntimeXamlLoaderDocument(uri, rootInstance, stream),
new RuntimeXamlLoaderConfiguration { DesignMode = designMode, LocalAssembly = localAssembly });
/// <summary>
/// Loads XAML from a stream.
/// </summary>
/// <param name="stream">The stream containing the XAML.</param>
/// <param name="document">The stream containing the XAML.</param>
/// <param name="configuration">Xaml loader configuration.</param>
/// <returns>The loaded object.</returns>
public static object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
=> AvaloniaXamlIlRuntimeCompiler.Load(stream, configuration.LocalAssembly, configuration.RootInstance,
configuration.BaseUri, configuration.DesignMode, configuration.UseCompiledBindingsByDefault);
public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration? configuration = null)
=> AvaloniaXamlIlRuntimeCompiler.Load(document, configuration ?? new RuntimeXamlLoaderConfiguration());
/// <summary>
/// Loads group of XAML files from a stream.
/// </summary>
/// <param name="documents">Collection of documents.</param>
/// <param name="configuration">Xaml loader configuration.</param>
/// <returns>The loaded objects per each input document.</returns>
public static IReadOnlyList<object> LoadGroup(IReadOnlyCollection<RuntimeXamlLoaderDocument> documents, RuntimeXamlLoaderConfiguration? configuration = null)
=> AvaloniaXamlIlRuntimeCompiler.LoadGroup(documents, configuration ?? new RuntimeXamlLoaderConfiguration());
/// <summary>
/// Parse XAML from a string.
@ -72,7 +66,7 @@ namespace Avalonia.Markup.Xaml
/// <param name="xaml">The string containing the XAML.</param>
/// <param name="localAssembly">Default assembly for clr-namespace:.</param>
/// <returns>The loaded object.</returns>
public static object Parse(string xaml, Assembly localAssembly = null)
public static object Parse(string xaml, Assembly? localAssembly = null)
=> Load(xaml, localAssembly);
/// <summary>
@ -82,7 +76,7 @@ namespace Avalonia.Markup.Xaml
/// <param name="xaml">>The string containing the XAML.</param>
/// <param name="localAssembly">>Default assembly for clr-namespace:.</param>
/// <returns>The loaded object.</returns>
public static T Parse<T>(string xaml, Assembly localAssembly = null)
public static T Parse<T>(string xaml, Assembly? localAssembly = null)
=> (T)Parse(xaml, localAssembly);
}

133
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Platform;
using XamlX.Ast;
using XamlX.Transform;
using XamlX.TypeSystem;
using XamlX.IL;
@ -150,12 +151,12 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault)
static object LoadSre(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
var success = false;
try
{
var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault);
var rv = LoadSreCore(document, configuration);
success = true;
return rv;
}
@ -166,45 +167,100 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
}
static IReadOnlyList<object> LoadGroupSre(IReadOnlyCollection<RuntimeXamlLoaderDocument> documents,
RuntimeXamlLoaderConfiguration configuration)
{
var success = false;
try
{
var rv = LoadGroupSreCore(documents, configuration);
success = true;
return rv;
}
finally
{
if( _sreCanSave)
DumpRuntimeCompilationResults();
}
}
static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault)
static IReadOnlyList<object> LoadGroupSreCore(IReadOnlyCollection<RuntimeXamlLoaderDocument> documents, RuntimeXamlLoaderConfiguration configuration)
{
InitializeSre();
var localAssembly = configuration.LocalAssembly;
if (localAssembly?.GetName() != null)
EmitIgnoresAccessCheckToAttribute(localAssembly.GetName());
var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly);
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
var clrPropertyBuilder = _sreBuilder.DefineType("ClrProperties_" + Guid.NewGuid().ToString("N"));
var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
_sreEmitMappings,
_sreContextType) { EnableIlVerification = true, DefaultCompileBindings = useCompiledBindingsByDefault };
_sreContextType)
{
EnableIlVerification = true,
DefaultCompileBindings = configuration.UseCompiledBindingsByDefault,
IsDesignMode = configuration.DesignMode
};
IXamlType overrideType = null;
if (rootInstance != null)
var parsedDocuments = new List<XamlDocumentResource>();
var rootInstances = new List<object>();
foreach (var document in documents)
{
overrideType = _sreTypeSystem.GetType(rootInstance.GetType());
string xaml;
using (var sr = new StreamReader(document.XamlStream))
xaml = sr.ReadToEnd();
IXamlType overrideType = null;
if (document.RootInstance != null)
{
overrideType = _sreTypeSystem.GetType(document.RootInstance.GetType());
}
var parsed = compiler.Parse(xaml, overrideType);
compiler.Transform(parsed);
var xamlName = GetSafeUriIdentifier(document.BaseUri)
?? document.RootInstance?.GetType().Name
?? ((IXamlAstValueNode)parsed.Root).Type.GetClrType().Name;
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + xamlName);
var builder = _sreTypeSystem.CreateTypeBuilder(tb);
parsedDocuments.Add(new XamlDocumentResource(parsed, document.BaseUri?.ToString(), null, null,
builder,
compiler.DefinePopulateMethod(builder, parsed, AvaloniaXamlIlCompiler.PopulateName, true),
compiler.DefineBuildMethod(builder, parsed, AvaloniaXamlIlCompiler.BuildName, true)));
rootInstances.Add(document.RootInstance);
}
compiler.IsDesignMode = isDesignMode;
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo();
compiler.TransformGroup(parsedDocuments);
var createdTypes = parsedDocuments.Select(document =>
{
compiler.Compile(document.XamlDocument, document.TypeBuilder, document.PopulateMethod,
document.BuildMethod, document.Uri, document.FileSource);
return _sreTypeSystem.GetType(document.TypeBuilder.CreateType());
}).ToArray();
clrPropertyBuilder.CreateTypeInfo();
indexerClosureType.CreateTypeInfo();
trampolineBuilder.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance);
return createdTypes.Zip(rootInstances, (l, r) => (l, r)).Select(t => LoadOrPopulate(t.Item1, t.Item2)).ToArray();
}
static object LoadSreCore(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
return LoadGroupSreCore(new[] { document }, configuration).Single();
}
#endif
static object LoadOrPopulate(Type created, object rootInstance)
static object LoadOrPopulate(Type created, object rootInstance)
{
var isp = Expression.Parameter(typeof(IServiceProvider));
@ -249,19 +305,37 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
}
public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri,
bool isDesignMode, bool useCompiledBindingsByDefault)
public static object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
#if RUNTIME_XAML_CECIL
string xaml;
using (var sr = new StreamReader(stream))
using (var sr = new StreamReader(document.XamlStream))
xaml = sr.ReadToEnd();
return LoadCecil(xaml, configuration.LocalAssembly, document.RootInstance,document.BaseUri, configuration.UseCompiledBindingsByDefault);
#else
return LoadSre(document, configuration);
#endif
}
public static IReadOnlyList<object> LoadGroup(IReadOnlyCollection<RuntimeXamlLoaderDocument> documents, RuntimeXamlLoaderConfiguration configuration)
{
#if RUNTIME_XAML_CECIL
return LoadCecil(xaml, localAssembly, rootInstance, uri, useCompiledBindingsByDefault);
throw new NotImplementedException("Load group was not implemented for the Cecil backend");
#else
return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault);
return LoadGroupSre(documents, configuration);
#endif
}
private static string GetSafeUriIdentifier(Uri uri)
{
return uri?.ToString()
.Replace(":", "_")
.Replace("/", "_")
.Replace("?", "_")
.Replace("=", "_")
.Replace(".", "_");
}
#if RUNTIME_XAML_CECIL
private static Dictionary<string, (Action<IServiceProvider, object> populate, Func<IServiceProvider, object>
build)>
@ -303,12 +377,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName);
}
var safeUri = uri.ToString()
.Replace(":", "_")
.Replace("/", "_")
.Replace("?", "_")
.Replace("=", "_")
.Replace(".", "_");
var safeUri = GetSafeUriIdentifier(uri);
if (_cecilGeneratedCache.TryGetValue(safeUri, out var cached))
return LoadOrPopulate(cached, rootInstance);
@ -335,7 +404,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
DefaultCompileBindings = useCompiledBindingsByDefault
};
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType);
compiler.ParseAndCompile(xaml, uri.ToString(), null, tb, overrideType);
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll");
using(var f = File.Create(asmPath))
asm.Write(f);

51
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast;
@ -56,8 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer(),
new AvaloniaXamlIlAssetIncludeTransformer()
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
InsertBefore<ConvertPropertyValuesToAssignmentsTransformer>(
new AvaloniaXamlIlOptionMarkupExtensionTransformer());
@ -83,6 +82,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Emitters.Add(new AvaloniaNameScopeRegistrationXamlIlNodeEmitter());
Emitters.Add(new AvaloniaXamlIlRootObjectScope.Emitter());
GroupTransformers = new()
{
new AvaloniaXamlIncludeTransformer()
};
}
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult> emitMappings,
@ -115,7 +119,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
set => _bindingTransformer.CompileBindingsByDefault = value;
}
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlType overrideRootType)
public List<IXamlAstGroupTransformer> GroupTransformers { get; }
public void TransformGroup(IReadOnlyCollection<IXamlDocumentResource> documents, bool strict = true)
{
var ctx = new AstGroupTransformationContext(documents, _configuration, strict);
foreach (var transformer in GroupTransformers)
{
foreach (var doc in documents)
{
var root = doc.XamlDocument.Root;
ctx.CurrentDocument = doc;
ctx.RootObject = (IXamlAstValueNode)root;
ctx.VisitChildren(ctx.RootObject, transformer);
root = ctx.Visit(root, transformer);
doc.XamlDocument.Root = root;
}
}
}
public XamlDocument Parse(string xaml, IXamlType overrideRootType)
{
var parsed = XDocumentXamlParser.Parse(xaml, new Dictionary<string, string>
{
@ -148,9 +172,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
OverrideRootType(parsed, rootType);
return parsed;
}
public void Compile(XamlDocument document, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlMethodBuilder<IXamlILEmitter> populateMethod, IXamlMethodBuilder<IXamlILEmitter> buildMethod, string baseUri, IFileSource fileSource)
{
Compile(document, _contextType, populateMethod, buildMethod,
_configuration.TypeMappings.XmlNamespaceInfoProvider == null ?
null :
tb.DefineSubType(_configuration.WellKnownTypes.Object,
"__AvaloniaXamlIlNsInfo", false), (name, bt) => tb.DefineSubType(bt, name, false),
(s, returnType, parameters) => tb.DefineDelegateSubType(s, false, returnType, parameters), baseUri,
fileSource);
}
public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlTypeBuilder<IXamlILEmitter> tb, IXamlType overrideRootType)
{
var parsed = Parse(xaml, overrideRootType);
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
}
public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -274,7 +274,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
var uriText = text.Trim();
var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.Absolute : UriKind.Relative);
var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.RelativeOrAbsolute : UriKind.Relative);
if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _))
{

78
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/IXamlAstGroupTransformer.cs

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Xml;
using XamlX;
using XamlX.Ast;
using XamlX.Transform;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
internal class AstGroupTransformationContext : AstTransformationContext
{
public AstGroupTransformationContext(IReadOnlyCollection<IXamlDocumentResource> documents, TransformerConfiguration configuration, bool strictMode = true)
: base(configuration, null, strictMode)
{
Documents = documents;
}
public IXamlDocumentResource CurrentDocument { get; set; }
public IReadOnlyCollection<IXamlDocumentResource> Documents { get; }
public new IXamlAstNode ParseError(string message, IXamlAstNode node) =>
Error(node, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, node));
public new IXamlAstNode ParseError(string message, IXamlAstNode offender, IXamlAstNode ret) =>
Error(ret, new XamlDocumentParseException(CurrentDocument?.FileSource?.FilePath, message, offender));
class Visitor : IXamlAstVisitor
{
private readonly AstGroupTransformationContext _context;
private readonly IXamlAstGroupTransformer _transformer;
public Visitor(AstGroupTransformationContext context, IXamlAstGroupTransformer transformer)
{
_context = context;
_transformer = transformer;
}
public IXamlAstNode Visit(IXamlAstNode node)
{
#if Xaml_DEBUG
return _transformer.Transform(_context, node);
#else
try
{
return _transformer.Transform(_context, node);
}
catch (Exception e) when (!(e is XmlException))
{
throw new XamlDocumentParseException(
_context.CurrentDocument?.FileSource?.FilePath,
"Internal compiler error while transforming node " + node + ":\n" + e,
node);
}
#endif
}
public void Push(IXamlAstNode node) => _context.PushParent(node);
public void Pop() => _context.PopParent();
}
public IXamlAstNode Visit(IXamlAstNode root, IXamlAstGroupTransformer transformer)
{
root = root.Visit(new Visitor(this, transformer));
return root;
}
public void VisitChildren(IXamlAstNode root, IXamlAstGroupTransformer transformer)
{
root.VisitChildren(new Visitor(this, transformer));
}
}
internal interface IXamlAstGroupTransformer
{
IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node);
}

173
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs

@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using Avalonia.Platform;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.Transform.Transformers;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer
{
public IXamlAstNode Transform(AstGroupTransformationContext context, IXamlAstNode node)
{
if (node is not XamlValueWithManipulationNode valueNode
|| valueNode.Value is not XamlAstNewClrObjectNode objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
return node;
}
var nodeTypeName = objectNode.Type.GetClrType().Name;
var expectedLoadedType = objectNode.Type.GetClrType().GetAllProperties()
.FirstOrDefault(p => p.Name == "Loaded")?.PropertyType;
if (expectedLoadedType is null)
{
throw new InvalidOperationException($"\"{nodeTypeName}\".Loaded property is expected to be defined");
}
if (valueNode.Manipulation is not XamlObjectInitializationNode
{
Manipulation: XamlPropertyAssignmentNode { Property: { Name: "Source" } } sourceProperty
})
{
return context.ParseError($"Source property must be set on the \"{nodeTypeName}\" node.", node);
}
// We expect that AvaloniaXamlIlLanguageParseIntrinsics has already parsed the Uri and created node like: `new Uri(assetPath, uriKind)`.
if (sourceProperty.Values.OfType<XamlAstNewClrObjectNode>().FirstOrDefault() is not { } sourceUriNode
|| sourceUriNode.Type.GetClrType() != context.GetAvaloniaTypes().Uri
|| sourceUriNode.Arguments.FirstOrDefault() is not XamlConstantNode { Constant: string originalAssetPath }
|| sourceUriNode.Arguments.Skip(1).FirstOrDefault() is not XamlConstantNode { Constant: int uriKind })
{
// TODO: make it a compiler warning
// Source value can be set with markup extension instead of the Uri object node, we don't support it here yet.
return node;
}
var uriPath = new Uri(originalAssetPath, (UriKind)uriKind);
if (!uriPath.IsAbsoluteUri)
{
var baseUrl = context.CurrentDocument.Uri ?? throw new InvalidOperationException("CurrentDocument URI is null.");
uriPath = new Uri(new Uri(baseUrl, UriKind.Absolute), uriPath);
}
else if (!uriPath.Scheme.Equals("avares", StringComparison.CurrentCultureIgnoreCase))
{
return context.ParseError(
$"\"{nodeTypeName}.Source\" supports only \"avares://\" absolute or relative uri.",
sourceUriNode, node);
}
var assetPathUri = Uri.UnescapeDataString(uriPath.AbsoluteUri);
var assetPath = assetPathUri.Replace("avares://", "");
var assemblyNameSeparator = assetPath.IndexOf('/');
var assembly = assetPath.Substring(0, assemblyNameSeparator);
var fullTypeName = Path.GetFileNameWithoutExtension(assetPath.Replace('/', '.'));
// Search file in the current assembly among other XAML resources.
if (context.Documents.FirstOrDefault(d => string.Equals(d.Uri, assetPathUri, StringComparison.InvariantCultureIgnoreCase)) is {} targetDocument)
{
if (targetDocument.BuildMethod is not null)
{
return FromMethod(context, targetDocument.BuildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly);
}
if (targetDocument.ClassType is not null)
{
return FromType(context, targetDocument.ClassType, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly);
}
return context.ParseError(
$"Unable to resolve XAML resource \"{assetPathUri}\" in the current assembly.",
sourceUriNode, node);
}
// If resource wasn't found in the current assembly, search in the others.
if (context.Configuration.TypeSystem.FindAssembly(assembly) is not { } assetAssembly)
{
return context.ParseError($"Assembly \"{assembly}\" was not found from the \"{assetPathUri}\" source.", sourceUriNode, node);
}
var avaResType = assetAssembly.FindType("CompiledAvaloniaXaml.!AvaloniaResources");
if (avaResType is null)
{
return context.ParseError(
$"Unable to resolve \"!AvaloniaResources\" type on \"{assembly}\" assembly.", sourceUriNode, node);
}
var relativeName = "Build:" + assetPath.Substring(assemblyNameSeparator);
var buildMethod = avaResType.FindMethod(m => m.Name == relativeName);
if (buildMethod is not null)
{
return FromMethod(context, buildMethod, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly);
}
else if (assetAssembly.FindType(fullTypeName) is { } type)
{
return FromType(context, type, sourceUriNode, expectedLoadedType, node, assetPathUri, assembly);
}
return context.ParseError(
$"Unable to resolve XAML resource \"{assetPathUri}\" in the \"{assembly}\" assembly.",
sourceUriNode, node);
}
private static IXamlAstNode FromType(AstTransformationContext context, IXamlType type, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly)
{
if (!expectedLoadedType.IsAssignableFrom(type))
{
return context.ParseError(
$"Resource \"{assetPathUri}\" is defined as \"{type}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
li, fallbackNode);
}
IXamlAstNode newObjNode = new XamlAstObjectNode(li, new XamlAstClrTypeReference(li, type, false));
newObjNode = new AvaloniaXamlIlConstructorServiceProviderTransformer().Transform(context, newObjNode);
newObjNode = new ConstructableObjectTransformer().Transform(context, newObjNode);
return new NewObjectTransformer().Transform(context, newObjNode);
}
private static IXamlAstNode FromMethod(AstTransformationContext context, IXamlMethod method, IXamlAstNode li,
IXamlType expectedLoadedType, IXamlAstNode fallbackNode, string assetPathUri, string assembly)
{
if (!expectedLoadedType.IsAssignableFrom(method.ReturnType))
{
return context.ParseError(
$"Resource \"{assetPathUri}\" is defined as \"{method.ReturnType}\" type in the \"{assembly}\" assembly, but expected \"{expectedLoadedType}\".",
li, fallbackNode);
}
var sp = context.Configuration.TypeMappings.ServiceProvider;
return new XamlStaticOrTargetedReturnMethodCallNode(li, method,
new[] { new NewServiceProviderNode(sp, li) });
}
internal class NewServiceProviderNode : XamlAstNode, IXamlAstValueNode,IXamlAstNodeNeedsParentStack,
IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
public NewServiceProviderNode(IXamlType type, IXamlLineInfo lineInfo) : base(lineInfo)
{
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlAstTypeReference Type { get; }
public bool NeedsParentStack => true;
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var method = context.GetAvaloniaTypes().RuntimeHelpers
.FindMethod(m => m.Name == "CreateRootServiceProviderV2");
codeGen.EmitCall(method);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
}

17
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/IXamlDocumentResource.cs

@ -0,0 +1,17 @@
using System;
using XamlX.Ast;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
#nullable enable
internal interface IXamlDocumentResource
{
IXamlMethod? BuildMethod { get; }
IXamlType? ClassType { get; }
string? Uri { get; }
IXamlMethod PopulateMethod { get; }
IFileSource? FileSource { get; }
XamlDocument XamlDocument { get; }
}

93
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlAssetIncludeTransformer.cs

@ -1,93 +0,0 @@
using System.Linq;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
internal class AvaloniaXamlIlAssetIncludeTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is not XamlAstObjectNode objectNode
|| (objectNode.Type.GetClrType() != context.GetAvaloniaTypes().StyleInclude
&& objectNode.Type.GetClrType() != context.GetAvaloniaTypes().ResourceInclude))
{
return node;
}
var nodeTypeName = objectNode.Type.GetClrType().Name;
var sourceProperty = objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().FirstOrDefault(n => n.Property.GetClrProperty().Name == "Source");
var directives = objectNode.Children.OfType<XamlAstXmlDirective>().ToList();
if (sourceProperty is null
|| objectNode.Children.Count != (directives.Count + 1))
{
throw new XamlParseException($"Unexpected property on the {nodeTypeName} node", node);
}
if (sourceProperty.Values.OfType<XamlAstTextNode>().FirstOrDefault() is not { } sourceTextNode)
{
// TODO: make it a compiler warning
// Source value can be set with markup extension instead of a text node, we don't support it here yet.
return node;
}
var originalAssetPath = sourceTextNode.Text;
if (!(originalAssetPath.StartsWith("avares://") || originalAssetPath.StartsWith("/")))
{
return node;
}
var runtimeHelpers = context.GetAvaloniaTypes().RuntimeHelpers;
var markerMethodName = "Resolve" + nodeTypeName;
var markerMethod = runtimeHelpers.FindMethod(m => m.Name == markerMethodName && m.Parameters.Count == 3);
if (markerMethod is null)
{
throw new XamlParseException($"Marker method \"{markerMethodName}\" was not found for the \"{nodeTypeName}\" node", node);
}
return new XamlValueWithManipulationNode(
node,
new AssetIncludeMethodNode(node, markerMethod, originalAssetPath),
new XamlManipulationGroupNode(node, directives));
}
private class AssetIncludeMethodNode : XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
{
private readonly IXamlMethod _method;
private readonly string _originalAssetPath;
public AssetIncludeMethodNode(
IXamlAstNode original, IXamlMethod method, string originalAssetPath)
: base(original)
{
_method = method;
_originalAssetPath = originalAssetPath;
}
public IXamlAstTypeReference Type => new XamlAstClrTypeReference(this, _method.ReturnType, false);
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
{
var absoluteSource = _originalAssetPath;
if (absoluteSource.StartsWith("/"))
{
// Avoid Uri class here to avoid potential problems with escaping.
// Keeping string as close to the original as possible.
var absoluteBaseUrl = context.RuntimeContext.BaseUrl;
absoluteSource = absoluteBaseUrl.Substring(0, absoluteBaseUrl.LastIndexOf('/')) + absoluteSource;
}
codeGen.Ldstr(absoluteSource);
codeGen.Ldc_I4(Line);
codeGen.Ldc_I4(Position);
codeGen.EmitCall(_method);
return XamlILNodeEmitResult.Type(0, _method.ReturnType);
}
}
}

9
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
@ -263,5 +264,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstGroupTransformationContext ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}
}
}

21
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentParseException.cs

@ -0,0 +1,21 @@
using XamlX;
using XamlX.Ast;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
internal class XamlDocumentParseException : XamlParseException
{
public string FilePath { get; }
public XamlDocumentParseException(string path, XamlParseException parseException)
: base(parseException.Message, parseException.LineNumber, parseException.LinePosition)
{
FilePath = path;
}
public XamlDocumentParseException(string path, string message, IXamlLineInfo lineInfo)
: base(message, lineInfo.Line, lineInfo.Position)
{
FilePath = path;
}
}

40
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlDocumentResource.cs

@ -0,0 +1,40 @@
using System;
using XamlX.Ast;
using XamlX.IL;
using XamlX.TypeSystem;
#nullable enable
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions;
internal class XamlDocumentResource : IXamlDocumentResource
{
public XamlDocumentResource(
XamlDocument xamlDocument,
string? uri,
IFileSource? fileSource,
IXamlType? classType,
IXamlTypeBuilder<IXamlILEmitter> typeBuilder,
IXamlMethodBuilder<IXamlILEmitter> populateMethod,
IXamlMethodBuilder<IXamlILEmitter>? buildMethod)
{
XamlDocument = xamlDocument;
Uri = uri;
FileSource = fileSource;
ClassType = classType;
TypeBuilder = typeBuilder;
PopulateMethod = populateMethod;
BuildMethod = buildMethod;
}
public XamlDocument XamlDocument { get; }
public string? Uri { get; }
public IFileSource? FileSource { get; }
public IXamlType? ClassType { get; }
public IXamlTypeBuilder<IXamlILEmitter> TypeBuilder { get; }
public IXamlMethodBuilder<IXamlILEmitter> PopulateMethod { get; }
public IXamlMethodBuilder<IXamlILEmitter>? BuildMethod { get; }
IXamlMethod? IXamlDocumentResource.BuildMethod => BuildMethod;
IXamlMethod IXamlDocumentResource.PopulateMethod => PopulateMethod;
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit cd3682c61577a3518de765f7938295d98ff9808c
Subproject commit 491de981dd4433ee58bc9540e2cd4a5d168f8168

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -43,6 +43,7 @@
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RuntimeXamlLoaderConfiguration.cs" />
<Compile Include="RuntimeXamlLoaderDocument.cs" />
<Compile Include="Styling\ResourceInclude.cs" />
<Compile Include="Styling\StyleInclude.cs" />
<Compile Include="Templates\ControlTemplate.cs" />

23
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -1,7 +1,7 @@
using System;
using System.IO;
using Avalonia.Platform;
#nullable enable
namespace Avalonia.Markup.Xaml
{
/// <summary>
@ -11,7 +11,7 @@ namespace Avalonia.Markup.Xaml
{
public interface IRuntimeXamlLoader
{
object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration);
object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration);
}
/// <summary>
@ -32,9 +32,10 @@ namespace Avalonia.Markup.Xaml
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>The loaded object.</returns>
public static object Load(Uri uri, Uri baseUri = null)
public static object Load(Uri uri, Uri? baseUri = null)
{
Contract.Requires<ArgumentNullException>(uri != null);
if (uri is null)
throw new ArgumentNullException(nameof(uri));
var assetLocator = AvaloniaLocator.Current.GetService<IAssetLoader>();
@ -44,14 +45,16 @@ namespace Avalonia.Markup.Xaml
"Could not create IAssetLoader : maybe Application.RegisterServices() wasn't called?");
}
var absoluteUri = uri.IsAbsoluteUri
? uri
: new Uri(baseUri ?? throw new InvalidOperationException("Cannot load relative Uri when BaseUri is null"), uri);
var compiledLoader = assetLocator.GetAssembly(uri, baseUri)
?.GetType("CompiledAvaloniaXaml.!XamlLoader")
?.GetMethod("TryLoad", new[] {typeof(string)});
if (compiledLoader != null)
{
var uriString = (!uri.IsAbsoluteUri && baseUri != null ? new Uri(baseUri, uri) : uri)
.ToString();
var compiledResult = compiledLoader.Invoke(null, new object[] {uriString});
var compiledResult = compiledLoader.Invoke(null, new object[] {absoluteUri.ToString()});
if (compiledResult != null)
return compiledResult;
}
@ -63,11 +66,9 @@ namespace Avalonia.Markup.Xaml
var asset = assetLocator.OpenAndGetAssembly(uri, baseUri);
using (var stream = asset.stream)
{
var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
return runtimeLoader.Load(stream, new RuntimeXamlLoaderConfiguration
return runtimeLoader.Load(new RuntimeXamlLoaderDocument(absoluteUri, stream), new RuntimeXamlLoaderConfiguration
{
LocalAssembly = asset.assembly,
BaseUri = absoluteUri
LocalAssembly = asset.assembly
});
}
}

2
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaUriTypeConverter.cs

@ -17,7 +17,7 @@ namespace Avalonia.Markup.Xaml.Converters
if (s == null)
return null;
//On Unix Uri tries to interpret paths starting with "/" as file Uris
var kind = s.StartsWith("/") ? UriKind.Relative : UriKind.Absolute;
var kind = s.StartsWith("/") ? UriKind.Relative : UriKind.RelativeOrAbsolute;
if (!Uri.TryCreate(s, kind, out var res))
throw new ArgumentException("Unable to parse URI: " + s);
return res;

13
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderConfiguration.cs

@ -1,4 +1,3 @@
using System;
using System.Reflection;
namespace Avalonia.Markup.Xaml;
@ -7,21 +6,11 @@ namespace Avalonia.Markup.Xaml;
public class RuntimeXamlLoaderConfiguration
{
/// <summary>
/// The URI of the XAML being loaded.
/// </summary>
public Uri? BaseUri { get; set; }
/// <summary>
/// Default assembly for clr-namespace:.
/// </summary>
public Assembly? LocalAssembly { get; set; }
/// <summary>
/// The optional instance into which the XAML should be loaded.
/// </summary>
public object? RootInstance { get; set; }
/// <summary>
/// Defines is CompiledBinding should be used by default.
/// Default is 'false'.

70
src/Markup/Avalonia.Markup.Xaml/RuntimeXamlLoaderDocument.cs

@ -0,0 +1,70 @@
#nullable enable
using System;
using System.IO;
using System.Text;
namespace Avalonia.Markup.Xaml;
public class RuntimeXamlLoaderDocument
{
public RuntimeXamlLoaderDocument(string xaml)
{
XamlStream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));
}
public RuntimeXamlLoaderDocument(Uri? baseUri, string xaml)
: this(xaml)
{
BaseUri = baseUri;
}
public RuntimeXamlLoaderDocument(object? rootInstance, string xaml)
: this(xaml)
{
RootInstance = rootInstance;
}
public RuntimeXamlLoaderDocument(Uri? baseUri, object? rootInstance, string xaml)
: this(baseUri, xaml)
{
RootInstance = rootInstance;
}
public RuntimeXamlLoaderDocument(Stream stream)
{
XamlStream = stream;
}
public RuntimeXamlLoaderDocument(Uri? baseUri, Stream stream)
: this(stream)
{
BaseUri = baseUri;
}
public RuntimeXamlLoaderDocument(object? rootInstance, Stream stream)
: this(stream)
{
RootInstance = rootInstance;
}
public RuntimeXamlLoaderDocument(Uri? baseUri, object? rootInstance, Stream stream)
: this(baseUri, stream)
{
RootInstance = rootInstance;
}
/// <summary>
/// The URI of the XAML being loaded.
/// </summary>
public Uri? BaseUri { get; set; }
/// <summary>
/// The optional instance into which the XAML should be loaded.
/// </summary>
public object? RootInstance { get; set; }
/// <summary>
/// The stream containing the XAML.
/// </summary>
public Stream XamlStream { get; set; }
}

3
src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs

@ -42,7 +42,8 @@ namespace Avalonia.Markup.Xaml.Styling
if (_loaded == null)
{
_isLoading = true;
_loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(Source, _baseUri);
var source = Source ?? throw new InvalidOperationException("ResourceInclude.Source must be set.");
_loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(source, _baseUri);
_isLoading = false;
}

3
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -51,7 +51,8 @@ namespace Avalonia.Markup.Xaml.Styling
if (_loaded == null)
{
_isLoading = true;
var loaded = (IStyle)AvaloniaXamlLoader.Load(Source, _baseUri);
var source = Source ?? throw new InvalidOperationException("StyleInclude.Source must be set.");
var loaded = (IStyle)AvaloniaXamlLoader.Load(source, _baseUri);
_loaded = new[] { loaded };
_isLoading = false;
}

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

@ -17,16 +17,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
public static class XamlIlRuntimeHelpers
{
public static IStyle ResolveStyleInclude(string absoluteSource, int line, int position)
{
return new StyleInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
}
public static IResourceDictionary ResolveResourceInclude(string absoluteSource, int line, int position)
{
return new ResourceInclude((Uri)null) { Source = new Uri(absoluteSource) }.Loaded;
}
public static Func<IServiceProvider, object> DeferredTransformationFactoryV1(Func<IServiceProvider, object> builder,
IServiceProvider provider)
{

6
src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs

@ -8,11 +8,9 @@ namespace Avalonia.Designer.HostApp
{
class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader
{
public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
{
return AvaloniaXamlIlRuntimeCompiler.Load(stream,
configuration.LocalAssembly, configuration.RootInstance, configuration.BaseUri,
configuration.DesignMode, configuration.UseCompiledBindingsByDefault);
return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration);
}
}
}

6
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -1642,10 +1642,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
X='{Binding StringProperty, DataType=local:TestDataContext}' />";
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml, new RuntimeXamlLoaderConfiguration
{
UseCompiledBindingsByDefault = true
});
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true });
var compiledPath = ((CompiledBindingExtension)control.X).Path;
var node = Assert.IsType<PropertyElement>(Assert.Single(compiledPath.Elements));

108
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/DynamicResourceExtensionTests.cs

@ -250,30 +250,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Setter_In_Styles_File()
{
var styleXaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Styles.Resources>
<Style Selector='Border'>
<Setter Property='Background' Value='{DynamicResource brush}'/>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
</Styles>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
<StyleInclude Source='avares://Tests/Style.xaml'/>
</Window.Styles>
<Border Name='border'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
</Window>")
};
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(compiled[1]);
var border = window.FindControl<Border>("border");
var brush = (ISolidColorBrush)border.Background;
@ -284,13 +286,14 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void DynamicResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File()
{
var styleXaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</Styles.Resources>
<Style Selector='Button'>
<Setter Property='Template'>
<ControlTemplate>
@ -298,20 +301,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</ControlTemplate>
</Setter>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
</Styles>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
<StyleInclude Source='avares://Tests/Style.xaml'/>
</Window.Styles>
<Button Name='button'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
</Window>")
};
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(compiled[1]);
var button = window.FindControl<Button>("button");
window.Show();
@ -553,35 +557,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files()
{
var style1Xaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style1.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>";
var style2Xaml = @"
</Style>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style2.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/>
</Style.Resources>
</Style>";
using (StyledWindow(
("test:style1.xaml", style1Xaml),
("test:style2.xaml", style2Xaml)))
{
var xaml = @"
</Style>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style1.xaml'/>
<StyleInclude Source='test:style2.xaml'/>
<StyleInclude Source='avares://Tests/Style1.xaml'/>
<StyleInclude Source='avares://Tests/Style2.xaml'/>
</Window.Styles>
<Border Name='border' Background='{DynamicResource RedBrush}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
</Window>")
};
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(compiled[2]);
var border = window.FindControl<Border>("border");
var borderBrush = (ISolidColorBrush)border.Background;
@ -593,33 +599,35 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void DynamicResource_Can_Be_Found_In_Nested_Style_File()
{
var style1Xaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style1.xaml"), @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<StyleInclude Source='test:style2.xaml'/>
</Styles>";
var style2Xaml = @"
<StyleInclude Source='avares://Tests/Style2.xaml'/>
</Styles>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style2.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/>
</Style.Resources>
</Style>";
using (StyledWindow(
("test:style1.xaml", style1Xaml),
("test:style2.xaml", style2Xaml)))
{
var xaml = @"
</Style>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style1.xaml'/>
<StyleInclude Source='avares://Tests/Style1.xaml'/>
</Window.Styles>
<Border Name='border' Background='{DynamicResource RedBrush}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
</Window>")
};
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(compiled[2]);
var border = window.FindControl<Border>("border");
var borderBrush = (ISolidColorBrush)border.Background;

44
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/ResourceIncludeTests.cs

@ -14,29 +14,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void ResourceInclude_Loads_ResourceDictionary()
{
var includeXaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resource.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush>
</ResourceDictionary>
";
using (StartWithResources(("test:include.xaml", includeXaml)))
{
var xaml = @"
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='test:include.xaml'/>
<ResourceInclude Source='avares://Tests/Resource.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Border Name='border' Background='{StaticResource brush}'/>
</UserControl>";
</UserControl>")
};
var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
using (StartWithResources())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var userControl = Assert.IsType<UserControl>(compiled[1]);
var border = userControl.FindControl<Border>("border");
var brush = (ISolidColorBrush)border.Background;
@ -47,31 +50,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void Missing_ResourceKey_In_ResourceInclude_Does_Not_Cause_StackOverflow()
{
var styleXaml = @"
var app = Application.Current;
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Resource.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<StaticResource x:Key='brush' ResourceKey='missing' />
</ResourceDictionary>";
using (StartWithResources(("test:style.xaml", styleXaml)))
{
var xaml = @"
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(app, @"
<Application xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='test:style.xaml'/>
<ResourceInclude Source='avares://Tests/Resource.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>";
var app = Application.Current;
</Application>")
};
using (StartWithResources())
{
try
{
AvaloniaRuntimeXamlLoader.Load(xaml, null, app);
AvaloniaRuntimeXamlLoader.LoadGroup(documents);
}
catch (KeyNotFoundException)
{

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

@ -238,7 +238,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void StaticResource_Can_Be_Assigned_To_Setter_In_Styles_File()
{
var styleXaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
@ -248,20 +250,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
<Style Selector='Border'>
<Setter Property='Background' Value='{StaticResource brush}'/>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
</Styles>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
<StyleInclude Source='avares://Tests/Style.xaml'/>
</Window.Styles>
<Border Name='border'/>
</Window>";
</Window>")
};
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(compiled[1]);
var border = window.FindControl<Border>("border");
var brush = (ISolidColorBrush)border.Background;
@ -311,7 +314,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Fact]
public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File()
{
var styleXaml = @"
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Styles.Resources>
@ -325,20 +330,21 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
</ControlTemplate>
</Setter>
</Style>
</Styles>";
using (StyledWindow(assets: ("test:style.xaml", styleXaml)))
{
var xaml = @"
</Styles>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Styles>
<StyleInclude Source='test:style.xaml'/>
<StyleInclude Source='avares://Tests/Style.xaml'/>
</Window.Styles>
<Button Name='button'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
</Window>")
};
using (StyledWindow())
{
var compiled = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(compiled[1]);
var button = window.FindControl<Button>("button");
window.Show();

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

@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests
{
public class StyleIncludeTests : XamlTestBase
{
[Fact]
public void Missing_ResourceKey_In_StyleInclude_Does_Not_Cause_StackOverflow()
{
var styleXaml = @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<StaticResource x:Key='brush' ResourceKey='missing' />
</Style.Resources>
</Style>";
using (StartWithResources(("test:style.xaml", styleXaml)))
{
var xaml = @"
<Application xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Application.Styles>
<StyleInclude Source='test:style.xaml'/>
</Application.Styles>
</Application>";
var app = Application.Current;
try
{
AvaloniaRuntimeXamlLoader.Load(xaml, null, app);
}
catch (KeyNotFoundException)
{
}
}
}
private IDisposable StartWithResources(params (string, string)[] assets)
{
var assetLoader = new MockAssetLoader(assets);
var services = new TestServices(assetLoader: assetLoader);
return UnitTestApplication.Start(services);
}
}
}

22
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -18,6 +18,7 @@ using System.Xml;
using Xunit;
using Avalonia.Controls.Documents;
using Avalonia.Metadata;
using Avalonia.Themes.Simple;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
@ -458,27 +459,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
Assert.Equal(10.0, d);
}
[Fact]
public void StyleInclude_Is_Built()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Styles xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<StyleInclude Source='avares://Avalonia.Themes.Simple/Controls/UserControl.xaml'/>
</Styles>";
var styles = AvaloniaRuntimeXamlLoader.Parse<Styles>(xaml);
Assert.True(styles.Count == 1);
var styleInclude = styles.First() as IStyle;
Assert.NotNull(styleInclude);
}
}
[Fact]
public void Simple_Xaml_Binding_Is_Operational()
{

22
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -33,29 +33,32 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
[Fact]
public void DynamicResource_Finds_Resource_In_Parent_Dictionary()
{
var dictionaryXaml = @"
using (StyledWindow())
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Avalonia.Markup.Xaml.UnitTests/dict.xaml"), @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/>
</ResourceDictionary>";
using (StyledWindow(assets: ("test:dict.xaml", dictionaryXaml)))
{
var xaml = @"
</ResourceDictionary>"),
new RuntimeXamlLoaderDocument(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='test:dict.xaml'/>
<ResourceInclude Source='avares://Avalonia.Markup.Xaml.UnitTests/dict.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<Color x:Key='Red'>Red</Color>
</Window.Resources>
<Button Name='button' Background='{DynamicResource RedBrush}'/>
</Window>";
</Window>")
};
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var loaded = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var window = Assert.IsType<Window>(loaded[1]);
var button = window.FindControl<Button>("button");
var brush = Assert.IsType<SolidColorBrush>(button.Background);
@ -276,7 +279,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(
assetLoader: new MockAssetLoader(assets),
theme: () => new Styles
{
WindowStyle(),

3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml

@ -1,6 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Precompile="False">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style.Resources>
<Color x:Key="Red">Red</Color>
<Color x:Key="Green">Green</Color>

3
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/Style2.xaml

@ -1,6 +1,5 @@
<Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Precompile="False">
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style.Resources>
<SolidColorBrush x:Key="RedBrush" Color="{DynamicResource Red}"/>
<SolidColorBrush x:Key="GreenBrush" Color="{DynamicResource Green}"/>

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

@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.Themes.Simple;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml;
public class StyleIncludeTests
{
[Fact]
public void StyleInclude_Is_Built()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'>
<ContentControl.Styles>
<StyleInclude Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Styles>
</ContentControl>";
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.IsType<Style>(window.Styles[0]);
}
}
[Fact]
public void StyleInclude_Is_Built_Resources()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Resources>
</ContentControl>";
var contentControl = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
}
[Fact]
public void StyleInclude_Is_Resolved_With_Two_Files()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(@"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='avares://Tests/Style.xaml'/>
</ContentControl.Resources>
</ContentControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var style = Assert.IsType<Style>(objects[0]);
var contentControl = Assert.IsType<ContentControl>(objects[1]);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
[Fact]
public void Relative_Back_StyleInclude_Is_Resolved_With_Two_Files()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Subfolder/Style.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Subfolder/Folder/Root.xaml"), @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='../Style.xaml'/>
</ContentControl.Resources>
</ContentControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var style = Assert.IsType<Style>(objects[0]);
var contentControl = Assert.IsType<ContentControl>(objects[1]);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
[Fact]
public void Relative_Root_StyleInclude_Is_Resolved_With_Two_Files()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='/Style.xaml'/>
</ContentControl.Resources>
</ContentControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var style = Assert.IsType<Style>(objects[0]);
var contentControl = Assert.IsType<ContentControl>(objects[1]);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
[Fact]
public void Relative_StyleInclude_Is_Resolved_With_Two_Files()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Style.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='Style.xaml'/>
</ContentControl.Resources>
</ContentControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var style = Assert.IsType<Style>(objects[0]);
var contentControl = Assert.IsType<ContentControl>(objects[1]);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
[Fact]
public void Relative_Dot_Syntax__StyleInclude_Is_Resolved_With_Two_Files()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Style.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Folder/Root.xaml"), @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='./Style.xaml'/>
</ContentControl.Resources>
</ContentControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var style = Assert.IsType<Style>(objects[0]);
var contentControl = Assert.IsType<ContentControl>(objects[1]);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
[Fact]
public void NonLatin_StyleInclude_Is_Resolved_With_Two_Files()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://アセンブリ/スタイル.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<Color x:Key='Red'>Red</Color>
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(@"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='avares://アセンブリ/スタイル.xaml'/>
</ContentControl.Resources>
</ContentControl>")
};
var objects = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
var style = Assert.IsType<Style>(objects[0]);
var contentControl = Assert.IsType<ContentControl>(objects[1]);
Assert.IsType<Style>(contentControl.Resources["Include"]);
}
[Fact]
public void Missing_ResourceKey_In_StyleInclude_Does_Not_Cause_StackOverflow()
{
var documents = new[]
{
new RuntimeXamlLoaderDocument(new Uri("avares://Tests/Style.xaml"), @"
<Style xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Style.Resources>
<StaticResource x:Key='brush' ResourceKey='missing' />
</Style.Resources>
</Style>"),
new RuntimeXamlLoaderDocument(@"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Styles>
<StyleInclude Source='avares://Tests/Style.xaml'/>
</ContentControl.Styles>
</ContentControl>")
};
try
{
_ = AvaloniaRuntimeXamlLoader.LoadGroup(documents);
}
catch (KeyNotFoundException)
{
}
}
[Fact]
public void StyleInclude_Should_Be_Replaced_With_Direct_Call()
{
var control = (ContentControl)AvaloniaRuntimeXamlLoader.Load(@"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:themes='clr-namespace:Avalonia.Themes.Simple;assembly=Avalonia.Themes.Simple'>
<ContentControl.Styles>
<themes:SimpleTheme />
<StyleInclude Source='avares://Avalonia.Themes.Simple/SimpleTheme.xaml'/>
</ContentControl.Styles>
</ContentControl>");
Assert.IsType<SimpleTheme>(control.Styles[0]);
Assert.IsType<SimpleTheme>(control.Styles[1]);
}
}

39
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs

@ -109,45 +109,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void StyleInclude_Is_Built()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'>
<ContentControl.Styles>
<StyleInclude Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Styles>
</ContentControl>";
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.IsType<Style>(window.Styles[0]);
}
}
[Fact]
public void StyleInclude_Is_Built_Resources()
{
using (UnitTestApplication.Start(TestServices.StyledWindow
.With(theme: () => new Styles())))
{
var xaml = @"
<ContentControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ContentControl.Resources>
<StyleInclude x:Key='Include' Source='avares://Avalonia.Markup.Xaml.UnitTests/Xaml/Style1.xaml'/>
</ContentControl.Resources>
</ContentControl>";
var window = AvaloniaRuntimeXamlLoader.Parse<ContentControl>(xaml);
Assert.IsType<Style>(window.Resources["Include"]);
}
}
[Fact]
public void Setter_Can_Contain_Template()
{

4
tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs

@ -20,8 +20,8 @@ namespace Avalonia.Markup.Xaml.UnitTests
class TestXamlLoaderShim : AvaloniaXamlLoader.IRuntimeXamlLoader
{
public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
=> AvaloniaRuntimeXamlLoader.Load(stream, configuration);
public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration)
=> AvaloniaRuntimeXamlLoader.Load(document, configuration);
}
}
}

Loading…
Cancel
Save