From de9e9580c00a0310e95d459ec4ad4b40e30fe9ad Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 25 Nov 2022 21:15:20 -0500 Subject: [PATCH] Pass parent's service provider to the StyleInclude, so parents stack is complete --- .../BuildEngineErrorCode.cs | 1 + .../XamlCompilerTaskExecutor.cs | 119 ++++++++++++++---- .../FluentTheme.xaml.cs | 8 +- .../SimpleTheme.xaml.cs | 8 +- .../XamlIncludeGroupTransformer.cs | 3 +- ...IlConstructorServiceProviderTransformer.cs | 12 ++ .../AvaloniaXamlLoader.cs | 41 +++++- .../StaticResourceExtension.cs | 21 +--- .../Styling/ResourceInclude.cs | 4 +- .../Styling/StyleInclude.cs | 4 +- .../XamlIl/Runtime/XamlIlRuntimeHelpers.cs | 28 +++-- .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 + .../Xaml/StyleIncludeTests.cs | 43 +++++++ .../Xaml/StyleWithServiceLocator.xaml | 5 + .../Xaml/StyleWithServiceLocator.xaml.cs | 16 +++ 15 files changed, 251 insertions(+), 63 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml.cs diff --git a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs index a149a758f4..a31c9a7516 100644 --- a/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs +++ b/src/Avalonia.Build.Tasks/BuildEngineErrorCode.cs @@ -7,6 +7,7 @@ namespace Avalonia.Build.Tasks LegacyResmScheme = 3, TransformError = 4, EmitError = 4, + Loader = 5, Unknown = 9999 } diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index ea2cf2cf99..a394d47904 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -196,7 +196,8 @@ namespace Avalonia.Build.Tasks var runtimeHelpers = typeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers"); var createRootServiceProviderMethod = asm.MainModule.ImportReference( typeSystem.GetTypeReference(runtimeHelpers).Resolve().Methods - .First(x => x.Name == "CreateRootServiceProviderV2")); + .First(x => x.Name == "CreateRootServiceProviderV3")); + var serviceProviderType = createRootServiceProviderMethod.ReturnType; var loaderDispatcherDef = new TypeDefinition(CompiledAvaloniaXamlNamespace, "!XamlLoader", TypeAttributes.Class | TypeAttributes.Public, asm.MainModule.TypeSystem.Object); @@ -212,11 +213,36 @@ namespace Avalonia.Build.Tasks MethodAttributes.Static | MethodAttributes.Public, asm.MainModule.TypeSystem.Object) { - Parameters = {new ParameterDefinition(asm.MainModule.TypeSystem.String)} + Parameters = + { + new ParameterDefinition(serviceProviderType), + new ParameterDefinition(asm.MainModule.TypeSystem.String) + }, + }; + var loaderDispatcherMethodOld = new MethodDefinition("TryLoad", + MethodAttributes.Static | MethodAttributes.Public, + asm.MainModule.TypeSystem.Object) + { + Parameters = + { + new ParameterDefinition(asm.MainModule.TypeSystem.String) + }, + Body = + { + Instructions = + { + Instruction.Create(OpCodes.Ldnull), + Instruction.Create(OpCodes.Ldarg_0), + Instruction.Create(OpCodes.Call, loaderDispatcherMethod), + Instruction.Create(OpCodes.Ret) + } + } }; loaderDispatcherDef.Methods.Add(loaderDispatcherMethod); + loaderDispatcherDef.Methods.Add(loaderDispatcherMethodOld); asm.MainModule.Types.Add(loaderDispatcherDef); + var stringEquals = asm.MainModule.ImportReference(asm.MainModule.TypeSystem.String.Resolve().Methods.First( m => m.IsStatic && m.Name == "Equals" && m.Parameters.Count == 3 && @@ -377,30 +403,42 @@ namespace Avalonia.Build.Tasks classTypeDefinition.Fields.Add(designLoaderField); const string TrampolineName = "!XamlIlPopulateTrampoline"; - var trampoline = new MethodDefinition(TrampolineName, - MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); - trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); - classTypeDefinition.Methods.Add(trampoline); + var trampolineMethodWithoutSP = new Lazy(() => CreateTrampolineMethod(false)); + var trampolineMethodWithSP = new Lazy(() => CreateTrampolineMethod(true)); + MethodDefinition CreateTrampolineMethod(bool hasSystemProviderArg) + { + var trampoline = new MethodDefinition(TrampolineName, + MethodAttributes.Static | MethodAttributes.Private, asm.MainModule.TypeSystem.Void); + if (hasSystemProviderArg) + { + trampoline.Parameters.Add(new ParameterDefinition(serviceProviderType)); + } + trampoline.Parameters.Add(new ParameterDefinition(classTypeDefinition)); + + classTypeDefinition.Methods.Add(trampoline); - var regularStart = Instruction.Create(OpCodes.Call, createRootServiceProviderMethod); + var regularStart = Instruction.Create(OpCodes.Nop); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Brfalse, regularStart)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldsfld, designLoaderField)); + trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, designLoaderLoad)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - trampoline.Body.Instructions.Add(regularStart); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); - trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - CopyDebugDocument(trampoline, compiledPopulateMethod); + trampoline.Body.Instructions.Add(regularStart); + trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_0 : OpCodes.Ldnull)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); + trampoline.Body.Instructions.Add(Instruction.Create(hasSystemProviderArg ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Call, compiledPopulateMethod)); + trampoline.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + CopyDebugDocument(trampoline, compiledPopulateMethod); + return trampoline; + } var foundXamlLoader = false; - // Find AvaloniaXamlLoader.Load(this) and replace it with !XamlIlPopulateTrampoline(this) - foreach (var method in classTypeDefinition.Methods - .Where(m => !m.Attributes.HasFlag(MethodAttributes.Static))) + // Find AvaloniaXamlLoader.Load(this) or AvaloniaXamlLoader.Load(sp, this) and replace it with !XamlIlPopulateTrampoline(this) + foreach (var method in classTypeDefinition.Methods.ToArray()) { var i = method.Body.Instructions; for (var c = 1; c < i.Count; c++) @@ -422,7 +460,20 @@ namespace Avalonia.Build.Tasks { if (MatchThisCall(i, c - 1)) { - i[c].Operand = trampoline; + i[c].Operand = trampolineMethodWithoutSP.Value; + foundXamlLoader = true; + } + } + if (op != null + && op.Name == "Load" + && op.Parameters.Count == 2 + && op.Parameters[0].ParameterType.FullName == "System.IServiceProvider" + && op.Parameters[1].ParameterType.FullName == "System.Object" + && op.DeclaringType.FullName == "Avalonia.Markup.Xaml.AvaloniaXamlLoader") + { + if (MatchThisCall(i, c - 1)) + { + i[c].Operand = trampolineMethodWithSP.Value; foundXamlLoader = true; } } @@ -439,7 +490,7 @@ namespace Avalonia.Build.Tasks { var i = ctors[0].Body.Instructions; var retIdx = i.IndexOf(i.Last(x => x.OpCode == OpCodes.Ret)); - i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampoline)); + i.Insert(retIdx, Instruction.Create(OpCodes.Call, trampolineMethodWithoutSP.Value)); i.Insert(retIdx, Instruction.Create(OpCodes.Ldarg_0)); } else @@ -461,20 +512,33 @@ namespace Avalonia.Build.Tasks null : classTypeDefinition.GetConstructors().FirstOrDefault(c => c.IsPublic && !c.IsStatic && !c.HasParameters); + var constructorWithSp = compiledBuildMethod != null ? + null : + classTypeDefinition.GetConstructors().FirstOrDefault(c => + c.IsPublic && !c.IsStatic && c.Parameters.Count == 1 && c.Parameters[0].ParameterType.FullName == serviceProviderType.FullName); - if (compiledBuildMethod != null || parameterlessConstructor != null) + if (compiledBuildMethod != null || parameterlessConstructor != null || constructorWithSp != null) { var i = loaderDispatcherMethod.Body.Instructions; var nop = Instruction.Create(OpCodes.Nop); - i.Add(Instruction.Create(OpCodes.Ldarg_0)); + i.Add(Instruction.Create(OpCodes.Ldarg_1)); i.Add(Instruction.Create(OpCodes.Ldstr, res.Uri)); i.Add(Instruction.Create(OpCodes.Ldc_I4, (int)StringComparison.OrdinalIgnoreCase)); i.Add(Instruction.Create(OpCodes.Call, stringEquals)); i.Add(Instruction.Create(OpCodes.Brfalse, nop)); if (parameterlessConstructor != null) + { i.Add(Instruction.Create(OpCodes.Newobj, parameterlessConstructor)); + } + else if (constructorWithSp != null) + { + i.Add(Instruction.Create(OpCodes.Ldarg_0)); + i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); + i.Add(Instruction.Create(OpCodes.Newobj, constructorWithSp)); + } else { + i.Add(Instruction.Create(OpCodes.Ldarg_0)); i.Add(Instruction.Create(OpCodes.Call, createRootServiceProviderMethod)); i.Add(Instruction.Create(OpCodes.Call, compiledBuildMethod)); } @@ -482,6 +546,11 @@ namespace Avalonia.Build.Tasks i.Add(Instruction.Create(OpCodes.Ret)); i.Add(nop); } + else + { + engine.LogWarning(BuildEngineErrorCode.Loader, "", + $"XAML resource \"{res.Uri}\" won't be reachable via runtime loader, as no public constructor was found"); + } } } diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs index 728e81b198..eea3d3ad08 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -31,9 +32,10 @@ namespace Avalonia.Themes.Fluent /// /// Initializes a new instance of the class. /// - public FluentTheme() + /// The parent's service provider. + public FluentTheme(IServiceProvider? sp = null) { - AvaloniaXamlLoader.Load(this); + AvaloniaXamlLoader.Load(sp, this); _baseDark = (IResourceDictionary)GetAndRemove("BaseDark"); _fluentDark = (IResourceDictionary)GetAndRemove("FluentDark"); diff --git a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs index af9d305043..56c4cbeac6 100644 --- a/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs +++ b/src/Avalonia.Themes.Simple/SimpleTheme.xaml.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; using Avalonia.Styling; @@ -16,9 +17,10 @@ namespace Avalonia.Themes.Simple /// /// Initializes a new instance of the class. /// - public SimpleTheme() + /// The parent's service provider. + public SimpleTheme(IServiceProvider? sp = null) { - AvaloniaXamlLoader.Load(this); + AvaloniaXamlLoader.Load(sp, this); _simpleDark = (IResourceDictionary)GetAndRemove("BaseDark"); _simpleLight = (IResourceDictionary)GetAndRemove("BaseLight"); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs index cc29d5ccb5..1bb05e238c 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/GroupTransformers/XamlIncludeGroupTransformer.cs @@ -163,8 +163,9 @@ internal class AvaloniaXamlIncludeTransformer : IXamlAstGroupTransformer public bool NeedsParentStack => true; public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) { + codeGen.Ldloc(context.ContextLocal); var method = context.GetAvaloniaTypes().RuntimeHelpers - .FindMethod(m => m.Name == "CreateRootServiceProviderV2"); + .FindMethod(m => m.Name == "CreateRootServiceProviderV3"); codeGen.EmitCall(method); return XamlILNodeEmitResult.Type(0, Type.GetClrType()); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs index 35e2624ff9..0304165995 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlConstructorServiceProviderTransformer.cs @@ -41,6 +41,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public bool NeedsParentStack => true; public XamlILNodeEmitResult Emit(XamlEmitContext context, IXamlILEmitter codeGen) { + if (_inheritContext) + { + codeGen.Ldloc(context.ContextLocal); + } + else + { + codeGen.Ldloc(context.ContextLocal); + var method = context.GetAvaloniaTypes().RuntimeHelpers + .FindMethod(m => m.Name == "CreateRootServiceProviderV3"); + codeGen.EmitCall(method); + } + codeGen.Ldloc(context.ContextLocal); return XamlILNodeEmitResult.Type(0, Type.GetClrType()); } diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs index 388ff61400..b5d222d979 100644 --- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs +++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs @@ -23,6 +23,17 @@ namespace Avalonia.Markup.Xaml throw new XamlLoadException( $"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource"); } + + /// + /// Loads the XAML into a Avalonia component. + /// + /// The parent's service provider. + /// The object to load the XAML into. + public static void Load(IServiceProvider? sp, object obj) + { + throw new XamlLoadException( + $"No precompiled XAML found for {obj.GetType()}, make sure to specify x:Class and include your XAML file as AvaloniaResource"); + } /// /// Loads XAML from a URI. @@ -33,6 +44,20 @@ namespace Avalonia.Markup.Xaml /// /// The loaded object. public static object Load(Uri uri, Uri? baseUri = null) + { + return Load(null, uri, baseUri); + } + + /// + /// Loads XAML from a URI. + /// + /// The parent's service provider. + /// The URI of the XAML file. + /// + /// A base URI to use if is relative. + /// + /// The loaded object. + public static object Load(IServiceProvider? sp, Uri uri, Uri? baseUri = null) { if (uri is null) throw new ArgumentNullException(nameof(uri)); @@ -51,13 +76,25 @@ namespace Avalonia.Markup.Xaml var compiledLoader = assetLocator.GetAssembly(uri, baseUri) ?.GetType("CompiledAvaloniaXaml.!XamlLoader") - ?.GetMethod("TryLoad", new[] {typeof(string)}); + ?.GetMethod("TryLoad", new[] { typeof(System.IServiceProvider), typeof(string) }); if (compiledLoader != null) { - var compiledResult = compiledLoader.Invoke(null, new object[] {absoluteUri.ToString()}); + var compiledResult = compiledLoader.Invoke(null, new object?[] { sp, absoluteUri.ToString()}); if (compiledResult != null) return compiledResult; } + else + { + compiledLoader = assetLocator.GetAssembly(uri, baseUri) + ?.GetType("CompiledAvaloniaXaml.!XamlLoader") + ?.GetMethod("TryLoad", new[] {typeof(string)}); + if (compiledLoader != null) + { + var compiledResult = compiledLoader.Invoke(null, new object?[] {absoluteUri.ToString()}); + if (compiledResult != null) + return compiledResult; + } + } // This is intended for unit-tests only var runtimeLoader = AvaloniaLocator.Current.GetService(); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index 5bff9d9ca6..84b4f3bdba 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -34,13 +34,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions _ => null, }; - if (provideTarget.TargetObject is Setter setter) + if (provideTarget.TargetObject is Setter { Property: not null } setter) { targetType = setter.Property.PropertyType; } - - var previousWasControlTheme = false; - + // Look upwards though the ambient context for IResourceNodes // which might be able to give us the resource. foreach (var parent in stack.Parents) @@ -49,21 +47,6 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { return ColorToBrushConverter.Convert(value, targetType); } - - // HACK: Temporary fix for #8678. Hard-coded to only work for the DevTools main - // window as we don't want 3rd parties to start relying on this hack. - // - // We need to implement compile-time merging of resource dictionaries and this - // hack can be removed. - if (previousWasControlTheme && - parent is IResourceProvider hack && - hack.Owner?.GetType().FullName == "Avalonia.Diagnostics.Views.MainWindow" && - hack.Owner.TryGetResource(ResourceKey, out value)) - { - return ColorToBrushConverter.Convert(value, targetType); - } - - previousWasControlTheme = parent is ControlTheme; } if (provideTarget.TargetObject is Control target && diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs index d7eb328715..8bbf233ed9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/ResourceInclude.cs @@ -10,6 +10,7 @@ namespace Avalonia.Markup.Xaml.Styling /// public class ResourceInclude : IResourceProvider { + private readonly IServiceProvider _serviceProvider; private readonly Uri? _baseUri; private IResourceDictionary? _loaded; private bool _isLoading; @@ -29,6 +30,7 @@ namespace Avalonia.Markup.Xaml.Styling /// The XAML service provider. public ResourceInclude(IServiceProvider serviceProvider) { + _serviceProvider = serviceProvider; _baseUri = serviceProvider.GetContextBaseUri(); } @@ -43,7 +45,7 @@ namespace Avalonia.Markup.Xaml.Styling { _isLoading = true; var source = Source ?? throw new InvalidOperationException("ResourceInclude.Source must be set."); - _loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(source, _baseUri); + _loaded = (IResourceDictionary)AvaloniaXamlLoader.Load(_serviceProvider, source, _baseUri); _isLoading = false; } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 2b75e0dd54..30e8ef4d02 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs @@ -12,6 +12,7 @@ namespace Avalonia.Markup.Xaml.Styling /// public class StyleInclude : IStyle, IResourceProvider { + private readonly IServiceProvider _serviceProvider; private readonly Uri? _baseUri; private IStyle[]? _loaded; private bool _isLoading; @@ -31,6 +32,7 @@ namespace Avalonia.Markup.Xaml.Styling /// The XAML service provider. public StyleInclude(IServiceProvider serviceProvider) { + _serviceProvider = serviceProvider; _baseUri = serviceProvider.GetContextBaseUri(); } @@ -52,7 +54,7 @@ namespace Avalonia.Markup.Xaml.Styling { _isLoading = true; var source = Source ?? throw new InvalidOperationException("StyleInclude.Source must be set."); - var loaded = (IStyle)AvaloniaXamlLoader.Load(source, _baseUri); + var loaded = (IStyle)AvaloniaXamlLoader.Load(_serviceProvider, source, _baseUri); _loaded = new[] { loaded }; _isLoading = false; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs index eec5d62fd3..2ca9a66fdc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs @@ -167,18 +167,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime #line hidden public static IServiceProvider CreateRootServiceProviderV2() { - return new RootServiceProvider(new NameScope()); + return new RootServiceProvider(new NameScope(), null); + } + public static IServiceProvider CreateRootServiceProviderV3(IServiceProvider parentServiceProvider) + { + return new RootServiceProvider(new NameScope(), parentServiceProvider); } #line default - class RootServiceProvider : IServiceProvider, IAvaloniaXamlIlParentStackProvider + class RootServiceProvider : IServiceProvider { private readonly INameScope _nameScope; + private readonly IServiceProvider _parentServiceProvider; private readonly IRuntimePlatform _runtimePlatform; - public RootServiceProvider(INameScope nameScope) + public RootServiceProvider(INameScope nameScope, IServiceProvider parentServiceProvider) { _nameScope = nameScope; + _parentServiceProvider = parentServiceProvider; _runtimePlatform = AvaloniaLocator.Current.GetService(); } @@ -187,19 +193,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime if (serviceType == typeof(INameScope)) return _nameScope; if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) - return this; + return _parentServiceProvider?.GetService() + ?? DefaultAvaloniaXamlIlParentStackProvider.Instance; if (serviceType == typeof(IRuntimePlatform)) return _runtimePlatform ?? throw new KeyNotFoundException($"{nameof(IRuntimePlatform)} was not registered"); return null; } - public IEnumerable Parents + private class DefaultAvaloniaXamlIlParentStackProvider : IAvaloniaXamlIlParentStackProvider { - get + public static DefaultAvaloniaXamlIlParentStackProvider Instance { get; } = new(); + + public IEnumerable Parents { - if (Application.Current != null) - yield return Application.Current; + get + { + if (Application.Current != null) + yield return Application.Current; + } } } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index bcb4bac457..a9528edc91 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -30,6 +30,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs index 8eed5013a2..f148d95bf9 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleIncludeTests.cs @@ -2,6 +2,10 @@ using System.Collections.Generic; using System.Linq; using Avalonia.Controls; +using Avalonia.Markup.Xaml.Styling; +using Avalonia.Markup.Xaml.XamlIl.Runtime; +using Avalonia.Media; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.Themes.Simple; using Avalonia.UnitTests; @@ -265,4 +269,43 @@ public class StyleIncludeTests Assert.IsType(control.Styles[0]); Assert.IsType(control.Styles[1]); } + + [Fact] + public void StyleInclude_From_CodeBehind_Resolves_Compiled() + { + using var locatorScope = AvaloniaLocator.EnterScope(); + AvaloniaLocator.CurrentMutable.BindToSelf(new AssetLoader(GetType().Assembly)); + + var sp = new TestServiceProvider(); + var styleInclude = new StyleInclude(sp) + { + Source = new Uri("avares://Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml") + }; + + var loaded = Assert.IsType(styleInclude.Loaded); + + Assert.Equal( + sp.GetService().Parents, + loaded.ServiceProvider.GetService().Parents); + } + + private class TestServiceProvider : IServiceProvider, IUriContext, IAvaloniaXamlIlParentStackProvider + { + private IServiceProvider _root = XamlIlRuntimeHelpers.CreateRootServiceProviderV2(); + public object GetService(Type serviceType) + { + if (serviceType == typeof(IUriContext)) + { + return this; + } + if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider)) + { + return this; + } + return _root.GetService(serviceType); + } + + public Uri BaseUri { get; set; } + public IEnumerable Parents { get; } = new[] { new ContentControl() }; + } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml new file mode 100644 index 0000000000..987c0f321a --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml @@ -0,0 +1,5 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml.cs new file mode 100644 index 0000000000..ceb122f05c --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleWithServiceLocator.xaml.cs @@ -0,0 +1,16 @@ +using System; +using Avalonia.Controls; +using Avalonia.Styling; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml; + +public class StyleWithServiceLocator : Style +{ + public IServiceProvider ServiceProvider { get; } + + public StyleWithServiceLocator(IServiceProvider sp = null) + { + ServiceProvider = sp; + AvaloniaXamlLoader.Load(sp, this); + } +}