|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
// Description:
// ResourceDictionary diagnostics API
// i. Enables enumeration of generic and themed ResourceDictionary instances.
// ii. Notifies listeners when generic or themed ResourceDictionary instances
// get loaded
// iii. Notifies listeners when themed ResourceDictionary instances get
// unloaded (generic ResourceDictionary instances are never unloaded)
using System.Collections.ObjectModel;
using System.Runtime.CompilerServices;
using System.Windows.Threading;
using MS.Internal;
using MS.Internal.Utility;
namespace System.Windows.Diagnostics
{
/// <summary>
/// Enables enumeration of generic and themed <see cref="ResourceDictionary"/> instances, and provides
/// a notification infrastructure for listening to loading and unloading of ResourceDictionary instances.
/// </summary>
/// <remarks>
/// This type supports the .NET Framework infrastructure and is not intended to be used directly
/// from application code.
/// </remarks>
public static class ResourceDictionaryDiagnostics
{
// Calls critical method IsEnvironmentVariableSet, but doesn't expose data
static ResourceDictionaryDiagnostics()
{
IsEnabled = VisualDiagnostics.IsEnabled &&
VisualDiagnostics.IsEnvironmentVariableSet(null, XamlSourceInfoHelper.XamlSourceInfoEnvironmentVariable);
// internal property, not visible to user
IgnorableProperties.Add(typeof(ResourceDictionary).GetProperty("DeferrableContent"));
}
#region Theme and Generic resource dictionaries
/// <summary>
/// When a managed debugger is attached, enumerates all instances of
/// themed <see cref="ResourceDictionary"/> instances loaded
/// by the application.
/// </summary>
public static IEnumerable<ResourceDictionaryInfo> ThemedResourceDictionaries
{
get
{
if (!IsEnabled)
{
return ResourceDictionaryDiagnostics.EmptyResourceDictionaryInfos;
}
return SystemResources.ThemedResourceDictionaries;
}
}
/// <summary>
/// When a managed debugger is attached, enumerates all instances of
/// generic <see cref="ResourceDictionary"/> instances loaded
/// by the application.
/// </summary>
public static IEnumerable<ResourceDictionaryInfo> GenericResourceDictionaries
{
get
{
if (!IsEnabled)
{
return ResourceDictionaryDiagnostics.EmptyResourceDictionaryInfos;
}
return SystemResources.GenericResourceDictionaries;
}
}
/// <summary>
/// Occurs when a managed debugger is attached, and a themed <see cref="ResourceDictionary"/> is loaded
/// by the application.
/// </summary>
public static event EventHandler<ResourceDictionaryLoadedEventArgs> ThemedResourceDictionaryLoaded
{
add
{
if (IsEnabled)
{
SystemResources.ThemedDictionaryLoaded += value;
}
}
remove
{
SystemResources.ThemedDictionaryLoaded -= value;
}
}
/// <summary>
/// Occurs when a managed debugger is attached, and a themed <see cref="ResourceDictionary"/> is unloaded
/// from the application.
/// </summary>
public static event EventHandler<ResourceDictionaryUnloadedEventArgs> ThemedResourceDictionaryUnloaded
{
add
{
if (IsEnabled)
{
SystemResources.ThemedDictionaryUnloaded += value;
}
}
remove
{
SystemResources.ThemedDictionaryUnloaded -= value;
}
}
/// <summary>
/// Occurs when a managed debugger is attached, and a generic <see cref="ResourceDictionary"/> is loaded
/// by the application.
/// </summary>
public static event EventHandler<ResourceDictionaryLoadedEventArgs> GenericResourceDictionaryLoaded
{
add
{
if(IsEnabled)
{
SystemResources.GenericDictionaryLoaded += value;
}
}
remove
{
SystemResources.GenericDictionaryLoaded -= value;
}
}
#endregion
#region Find ResourceDictionaries created from a given source Uri
public static IEnumerable<ResourceDictionary> GetResourceDictionariesForSource(Uri uri)
{
if (!IsEnabled || _dictionariesFromUri == null)
{
return EmptyResourceDictionaries;
}
lock (_dictionariesFromUriLock)
{
List<WeakReference<ResourceDictionary>> list;
if (!_dictionariesFromUri.TryGetValue(uri, out list) || list.Count == 0)
{
return EmptyResourceDictionaries;
}
List<ResourceDictionary> result = new List<ResourceDictionary>(list.Count);
List<WeakReference<ResourceDictionary>> toRemove = null;
foreach (WeakReference<ResourceDictionary> wr in list)
{
ResourceDictionary rd;
if (wr.TryGetTarget(out rd))
{
result.Add(rd);
}
else
{
// stale entry - mark for removal (can't remove while iterating over list)
if (toRemove == null)
{
toRemove = new List<WeakReference<ResourceDictionary>>();
}
toRemove.Add(wr);
}
}
// if we found stale entries, remove them now
if (toRemove != null)
{
RemoveEntries(uri, list, toRemove);
}
return result.AsReadOnly();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AddResourceDictionaryForUri(Uri uri, ResourceDictionary rd)
{
if (uri != null && IsEnabled)
{
AddResourceDictionaryForUriImpl(uri, rd);
}
}
private static void AddResourceDictionaryForUriImpl(Uri uri, ResourceDictionary rd)
{
lock (_dictionariesFromUriLock)
{
List<WeakReference<ResourceDictionary>> list;
if (_dictionariesFromUri == null)
{
_dictionariesFromUri = new Dictionary<Uri, List<WeakReference<ResourceDictionary>>>();
}
if (!_dictionariesFromUri.TryGetValue(uri, out list))
{
list = new List<WeakReference<ResourceDictionary>>(1);
_dictionariesFromUri.Add(uri, list);
}
list.Add(new WeakReference<ResourceDictionary>(rd));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void RemoveResourceDictionaryForUri(Uri uri, ResourceDictionary rd)
{
if (uri != null && IsEnabled)
{
RemoveResourceDictionaryForUriImpl(uri, rd);
}
}
private static void RemoveResourceDictionaryForUriImpl(Uri uri, ResourceDictionary rd)
{
lock (_dictionariesFromUriLock)
{
if (_dictionariesFromUri != null)
{
List<WeakReference<ResourceDictionary>> list;
if (_dictionariesFromUri.TryGetValue(uri, out list))
{
List<WeakReference<ResourceDictionary>> toRemove = new List<WeakReference<ResourceDictionary>>();
foreach (WeakReference<ResourceDictionary> wr in list)
{
ResourceDictionary target;
if (!wr.TryGetTarget(out target) || target == rd)
{
toRemove.Add(wr);
}
}
RemoveEntries(uri, list, toRemove);
}
}
}
}
private static void RemoveEntries(Uri uri,
List<WeakReference<ResourceDictionary>> list,
List<WeakReference<ResourceDictionary>> toRemove)
{
foreach (WeakReference<ResourceDictionary> wr in toRemove)
{
list.Remove(wr);
}
if (list.Count == 0)
{
_dictionariesFromUri.Remove(uri);
}
}
private static Dictionary<Uri, List<WeakReference<ResourceDictionary>>> _dictionariesFromUri;
private static readonly object _dictionariesFromUriLock = new object();
private static IReadOnlyCollection<ResourceDictionary> EmptyResourceDictionaries
=> Array.Empty<ResourceDictionary>();
#endregion
#region Find owners of ResourceDictionary
public static IEnumerable<FrameworkElement> GetFrameworkElementOwners(ResourceDictionary dictionary)
{
return GetOwners<FrameworkElement>(dictionary.FrameworkElementOwners, EmptyFrameworkElementList);
}
public static IEnumerable<FrameworkContentElement> GetFrameworkContentElementOwners(ResourceDictionary dictionary)
{
return GetOwners<FrameworkContentElement>(dictionary.FrameworkContentElementOwners, EmptyFrameworkContentElementList);
}
public static IEnumerable<Application> GetApplicationOwners(ResourceDictionary dictionary)
{
return GetOwners<Application>(dictionary.ApplicationOwners, EmptyApplicationList);
}
private static IEnumerable<T> GetOwners<T>(WeakReferenceList list, IEnumerable<T> emptyList)
where T : DispatcherObject
{
if (!IsEnabled || list == null || list.Count == 0)
{
return emptyList;
}
List<T> result = new List<T>(list.Count);
foreach (Object o in list)
{
T owner = o as T;
if (owner != null)
{
result.Add(owner);
}
}
return result.AsReadOnly();
}
private static IReadOnlyCollection<FrameworkElement> EmptyFrameworkElementList
=> Array.Empty<FrameworkElement>();
private static IReadOnlyCollection<FrameworkContentElement> EmptyFrameworkContentElementList
=> Array.Empty<FrameworkContentElement>();
private static IReadOnlyCollection<Application> EmptyApplicationList
=> Array.Empty<Application>();
#endregion
#region Notify when static resource references are resolved
public static event EventHandler<StaticResourceResolvedEventArgs> StaticResourceResolved;
internal static bool HasStaticResourceResolvedListeners
{
get { return IsEnabled && (StaticResourceResolved != null); }
}
internal static bool ShouldIgnoreProperty(object targetProperty)
{
return IgnorableProperties.Contains(targetProperty);
}
internal static LookupResult RequestLookupResult(StaticResourceExtension requester)
{
// requests can be nested - e.g. when one request resolves to a
// resource whose value is deferred content that gets inflated.
// So we need a (thread static) stack of active requests.
if (_lookupResultStack == null)
{
_lookupResultStack = new Stack<LookupResult>();
}
LookupResult result = new LookupResult(requester);
_lookupResultStack.Push(result);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void RecordLookupResult(object key, ResourceDictionary rd)
{
if (IsEnabled && _lookupResultStack != null)
{
RecordLookupResultImpl(key, rd);
}
}
private static void RecordLookupResultImpl(object key, ResourceDictionary rd)
{
if (_lookupResultStack.Count > 0)
{
// get the active request
LookupResult result = _lookupResultStack.Peek();
// if the resolution is for some other key, ignore it
// (this can happen when inflating deferred content)
if (!Object.Equals(result.Requester.ResourceKey, key))
return;
// simple case - no deferred content, no lookup optimizations
if (result.Requester.GetType() == typeof(StaticResourceExtension))
{
Debug.Assert(result.Dictionary == null || result.Dictionary == rd,
"simple lookup resolved more than once");
result.Key = key;
result.Dictionary = rd;
return;
}
// the requester is a derived class of StaticResourceExtension
// (notably StaticResourceHolder, the only built-in derived class).
// This introduces complications:
// 1. The class caches the result of its lookup the first time,
// and returns the cached value thereafter. This avoids
// the lookup, and thus also avoids RD's call to this method.
// 2. The initial lookup can happen twice, the second time
// to cope with deferred content including a closer definition
// (see StaticResourceExtension.FindResourceInDeferredContent).
// For (2), retain the latest recorded result.
result.Key = key;
result.Dictionary = rd;
// There's a potential bug here, with no fix/workaround of reasonable
// cost. The two lookups cited in (2) will both resolve with the
// right request at the top of the stack - good. But if there is
// a subsequent lookup for the same key from some other
// (non-StaticResourceExtension) source made while the request is
// still on top, its result will get recorded here - bad, it might
// yield the wrong dictionary.
// This would have to be a complex situation involving deferred
// content with nested dictionaries providing alternative definitions
// for the same key, and with that "subsequent lookup".
// I can't get it to happen (especially the "subsequent lookup"
// part), but I'm not 100% certain it can't. But as it would
// only impact diagnostics, and that only in a limited way, it's
// not worth any more effort.
}
}
internal static void RevertRequest(StaticResourceExtension requester, bool success)
{
LookupResult result = _lookupResultStack.Pop();
Debug.Assert(Object.Equals(result.Requester, requester), "Reverting a request for the wrong requester");
// if the request failed, nothing more to do
if (!success)
return;
// simple case - no deferred content, no lookup optimizations
if (result.Requester.GetType() == typeof(StaticResourceExtension))
{
Debug.Assert(result.Dictionary != null, "simple lookup resolved incorrectly");
return;
}
// complex case (see (1) in remarks in RecordLookupResultImpl above)
// Maintain a cache mapping requester to the dictionary it used.
// Use weak references to avoid memory leaks.
if (_resultCache == null)
{
_resultCache = new Dictionary<WeakReferenceKey<StaticResourceExtension>, WeakReference<ResourceDictionary>>();
}
WeakReferenceKey<StaticResourceExtension> wrKey = new WeakReferenceKey<StaticResourceExtension>(requester);
WeakReference<ResourceDictionary> wrDict;
ResourceDictionary cachedDict = null;
bool found = _resultCache.TryGetValue(wrKey, out wrDict);
if (found)
{
wrDict.TryGetTarget(out cachedDict);
}
if (result.Dictionary != null)
{
// The first time the class requests a value, cache the dictionary
if (cachedDict != null)
{
Debug.Assert(cachedDict == result.Dictionary, "conflicting dictionaries for a StaticResource reference");
}
_resultCache[wrKey] = new WeakReference<ResourceDictionary>(result.Dictionary);
}
else
{
// for subsequent (optimized) requests, use the dictionary from the cache
Debug.Assert(cachedDict != null, "no dictionary found for StaticResource reference");
result.Key = requester.ResourceKey;
result.Dictionary = cachedDict;
}
}
internal static void OnStaticResourceResolved(object targetObject, object targetProperty, LookupResult result)
{
EventHandler<StaticResourceResolvedEventArgs> handler = StaticResourceResolved;
if (handler != null && result.Dictionary != null)
{
handler(null, new StaticResourceResolvedEventArgs(
targetObject,
targetProperty,
result.Dictionary,
result.Key));
}
RequestCacheCleanup(targetObject);
}
private static void RequestCacheCleanup(object targetObject)
{
DispatcherObject d;
Dispatcher dispatcher;
// ignore if cleanup is not needed, already requested, or not requestable
if (_resultCache == null ||
_cleanupOperation != null ||
(d = targetObject as DispatcherObject) == null ||
(dispatcher = d.Dispatcher) == null)
{
return;
}
// request a cleanup at low priority
_cleanupOperation = dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)DoCleanup);
}
// purge the cache of stale entries
private static void DoCleanup()
{
_cleanupOperation = null;
List<WeakReferenceKey<StaticResourceExtension>> toRemove = null;
foreach (KeyValuePair<WeakReferenceKey<StaticResourceExtension>, WeakReference<ResourceDictionary>>
kvp in _resultCache)
{
ResourceDictionary dict;
if (kvp.Key.Item == null || !kvp.Value.TryGetTarget(out dict))
{
if (toRemove == null)
{
toRemove = new List<WeakReferenceKey<StaticResourceExtension>>();
}
toRemove.Add(kvp.Key);
}
}
if (toRemove != null)
{
foreach (WeakReferenceKey<StaticResourceExtension> wrKey in toRemove)
{
_resultCache.Remove(wrKey);
}
}
}
private static List<object> IgnorableProperties = new List<object>();
// these are [ThreadStatic] because two threads can be resolving StaticResource simultaneously
[ThreadStatic]
private static Stack<LookupResult> _lookupResultStack;
[ThreadStatic]
private static Dictionary<WeakReferenceKey<StaticResourceExtension>, WeakReference<ResourceDictionary>>
_resultCache;
[ThreadStatic]
private static DispatcherOperation _cleanupOperation;
internal class LookupResult
{
public StaticResourceExtension Requester { get; set; }
public Object Key { get; set; }
public ResourceDictionary Dictionary { get; set; }
public LookupResult(StaticResourceExtension requester) { Requester = requester; }
}
#endregion
internal static bool IsEnabled { get; private set; }
private static readonly ReadOnlyCollection<ResourceDictionaryInfo> EmptyResourceDictionaryInfos
= new List<ResourceDictionaryInfo>().AsReadOnly();
}
}
|