[X] Allow tracking the assignment of StaticResource (#6868)

This allows advanced tracking of SR for debugging purposes. It doesn't affects
apps running without the debugger.

See also https://docs.microsoft.com/en-us/dotnet/api/system.windows.diagnostics.resourcedictionarydiagnostics
This commit is contained in:
Stephane Delcroix 2019-07-29 23:09:26 +02:00 коммит произвёл Samantha Houts
Родитель 88577eac76
Коммит 897242a003
6 изменённых файлов: 104 добавлений и 41 удалений

Просмотреть файл

@ -260,19 +260,25 @@ namespace Xamarin.Forms
}
public bool TryGetValue(string key, out object value)
=> TryGetValueAndSource(key, out value, out _);
internal bool TryGetValueAndSource(string key, out object value, out ResourceDictionary source)
{
source = this;
return _innerDictionary.TryGetValue(key, out value)
|| (_mergedInstance != null && _mergedInstance.TryGetValue(key, out value))
|| (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value));
|| (_mergedInstance != null && _mergedInstance.TryGetValueAndSource(key, out value, out source))
|| (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value, out source));
}
bool TryGetMergedDictionaryValue(string key, out object value)
bool TryGetMergedDictionaryValue(string key, out object value, out ResourceDictionary source)
{
foreach (var dictionary in MergedDictionaries.Reverse())
if (dictionary.TryGetValue(key, out value))
if (dictionary.TryGetValue(key, out value)) {
source = dictionary;
return true;
}
value = null;
value = null; source = null;
return false;
}

Просмотреть файл

@ -0,0 +1,10 @@
using System;
namespace Xamarin.Forms.Xaml.Diagnostics
{
internal static class ResourceDictionaryDiagnostics
{
internal static void OnStaticResourceResolved(ResourceDictionary resourceDictionary, string key, object targetObject, object targetProperty)
=> StaticResourceResolved?.Invoke(resourceDictionary, new StaticResourceResolvedEventArgs(resourceDictionary, key, targetObject, targetProperty));
public static event EventHandler<StaticResourceResolvedEventArgs> StaticResourceResolved;
}
}

Просмотреть файл

@ -0,0 +1,19 @@
using System;
namespace Xamarin.Forms.Xaml.Diagnostics
{
internal class StaticResourceResolvedEventArgs : EventArgs
{
internal StaticResourceResolvedEventArgs(ResourceDictionary resourceDictionary, string key, object targetObject, object targetProperty)
{
ResourceDictionary = resourceDictionary;
Key = key;
TargetObject = targetObject;
TargetProperty = targetProperty;
}
public ResourceDictionary ResourceDictionary { get; }
public string Key { get; }
public object TargetObject { get; }
public object TargetProperty { get; }
}
}

Просмотреть файл

@ -3,14 +3,16 @@ using System.Reflection;
using System.Xml;
using System.Linq;
using Xamarin.Forms.Internals;
using System.Collections.Generic;
namespace Xamarin.Forms.Xaml
{
[ContentProperty(nameof(Key))]
public sealed class StaticResourceExtension : IMarkupExtension
{
public string Key { get; set; }
internal static bool _mockDebuggerIsAttached; //for unit testing
public string Key { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (serviceProvider == null)
@ -21,64 +23,86 @@ namespace Xamarin.Forms.Xaml
throw new ArgumentException();
var xmlLineInfo = serviceProvider.GetService(typeof(IXmlLineInfoProvider)) is IXmlLineInfoProvider xmlLineInfoProvider ? xmlLineInfoProvider.XmlLineInfo : null;
object resource = null;
foreach (var p in valueProvider.ParentObjects) {
var resDict = p is IResourcesProvider irp && irp.IsResourcesCreated ? irp.Resources : p as ResourceDictionary;
if (resDict == null)
continue;
if (resDict.TryGetValue(Key, out resource))
break;
}
resource = resource ?? GetApplicationLevelResource(Key, xmlLineInfo);
if ( !TryGetResource(Key, valueProvider.ParentObjects, out var resource, out var resourceDictionary)
&& !TryGetApplicationLevelResource(Key, out resource, out resourceDictionary))
throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
var bp = valueProvider.TargetProperty as BindableProperty;
var pi = valueProvider.TargetProperty as PropertyInfo;
if (System.Diagnostics.Debugger.IsAttached || _mockDebuggerIsAttached)
Diagnostics.ResourceDictionaryDiagnostics.OnStaticResourceResolved(resourceDictionary, Key, valueProvider.TargetObject, valueProvider.TargetProperty);
return CastTo(resource, valueProvider.TargetProperty);
}
//used by X.HR.F
internal static object CastTo(object value, object targetProperty)
{
var bp = targetProperty as BindableProperty;
var pi = targetProperty as PropertyInfo;
var propertyType = bp?.ReturnType ?? pi?.PropertyType;
if (propertyType == null) {
if (resource.GetType().GetTypeInfo().IsGenericType && (resource.GetType().GetGenericTypeDefinition() == typeof(OnPlatform<>) || resource.GetType().GetGenericTypeDefinition() == typeof(OnIdiom<>))) {
if (propertyType == null)
{
if (value.GetType().GetTypeInfo().IsGenericType && (value.GetType().GetGenericTypeDefinition() == typeof(OnPlatform<>) || value.GetType().GetGenericTypeDefinition() == typeof(OnIdiom<>)))
{
// This is only there to support our backward compat story with pre 2.3.3 compiled Xaml project who was not providing TargetProperty
var method = resource.GetType().GetRuntimeMethod("op_Implicit", new[] { resource.GetType() });
resource = method.Invoke(null, new[] { resource });
var method = value.GetType().GetRuntimeMethod("op_Implicit", new[] { value.GetType() });
value = method.Invoke(null, new[] { value });
}
return resource;
return value;
}
if (propertyType.IsAssignableFrom(resource.GetType()))
return resource;
var implicit_op = resource.GetType().GetImplicitConversionOperator(fromType: resource.GetType(), toType: propertyType)
?? propertyType.GetImplicitConversionOperator(fromType: resource.GetType(), toType: propertyType);
if (propertyType.IsAssignableFrom(value.GetType()))
return value;
var implicit_op = value.GetType().GetImplicitConversionOperator(fromType: value.GetType(), toType: propertyType)
?? propertyType.GetImplicitConversionOperator(fromType: value.GetType(), toType: propertyType);
if (implicit_op != null)
return implicit_op.Invoke(resource, new [] { resource });
return implicit_op.Invoke(value, new[] { value });
//Special case for https://bugzilla.xamarin.com/show_bug.cgi?id=59818
//On OnPlatform, check for an opImplicit from the targetType
if ( Device.Flags != null
if (Device.Flags != null
&& Device.Flags.Contains("xamlDoubleImplicitOpHack")
&& resource.GetType().GetTypeInfo().IsGenericType
&& (resource.GetType().GetGenericTypeDefinition() == typeof(OnPlatform<>))) {
var tType = resource.GetType().GenericTypeArguments[0];
&& value.GetType().GetTypeInfo().IsGenericType
&& (value.GetType().GetGenericTypeDefinition() == typeof(OnPlatform<>)))
{
var tType = value.GetType().GenericTypeArguments[0];
var opImplicit = tType.GetImplicitConversionOperator(fromType: tType, toType: propertyType)
?? propertyType.GetImplicitConversionOperator(fromType: tType, toType: propertyType);
if (opImplicit != null) {
if (opImplicit != null)
{
//convert the OnPlatform<T> to T
var opPlatformImplicitConversionOperator = resource.GetType().GetImplicitConversionOperator(fromType: resource.GetType(), toType: tType);
resource = opPlatformImplicitConversionOperator.Invoke(null, new[] { resource });
var opPlatformImplicitConversionOperator = value.GetType().GetImplicitConversionOperator(fromType: value.GetType(), toType: tType);
value = opPlatformImplicitConversionOperator.Invoke(null, new[] { value });
//and convert to toType
resource = opImplicit.Invoke(null, new[] { resource });
return resource;
value = opImplicit.Invoke(null, new[] { value });
return value;
}
}
return resource;
return value;
}
internal object GetApplicationLevelResource(string key, IXmlLineInfo xmlLineInfo)
bool TryGetResource(string key, IEnumerable<object> parentObjects, out object resource, out ResourceDictionary resourceDictionary)
{
if (Application.Current == null || !((IResourcesProvider)Application.Current).IsResourcesCreated || !Application.Current.Resources.TryGetValue(Key, out object resource))
throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
return resource;
resource = null;
resourceDictionary = null;
foreach (var p in parentObjects) {
var resDict = p is IResourcesProvider irp && irp.IsResourcesCreated ? irp.Resources : p as ResourceDictionary;
if (resDict == null)
continue;
if (resDict.TryGetValueAndSource(Key, out resource, out resourceDictionary))
return true;
}
return false;
}
bool TryGetApplicationLevelResource(string key, out object resource, out ResourceDictionary resourceDictionary)
{
resource = null;
resourceDictionary = null;
return Application.Current != null && ((IResourcesProvider)Application.Current).IsResourcesCreated && Application.Current.Resources.TryGetValueAndSource (key, out resource, out resourceDictionary);
}
}
}

Просмотреть файл

@ -8,6 +8,7 @@ using Xamarin.Forms.Internals;
[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.Design")]
[assembly: InternalsVisibleTo("Xamarin.Forms.Loader")]// Xamarin.Forms.Loader.dll Xamarin.Forms.Xaml.XamlLoader.Load(object, string), kzu@microsoft.com
[assembly: InternalsVisibleTo("Xamarin.HotReload.Forms")]
[assembly: InternalsVisibleTo("Xamarin.HotReload.UnitTests")]
[assembly: Preserve]
[assembly: XmlnsDefinition("http://xamarin.com/schemas/2014/forms", "Xamarin.Forms.Xaml")]

Просмотреть файл

@ -10,4 +10,7 @@
<ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Diagnostics\" />
</ItemGroup>
</Project>