|
// 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:
// Helper class to expose XamlSourceInfo for objects loaded from BAML or XAML
// for diagnostic scenarios. Source info will be stored as an attached dependency
// property on dependency objects. Because storing additional data increases
// memory footprint we will turn this on when ENABLE_XAML_DIAGNOSTICS_SOURCE_INFO
// environment variable is present and its trimmed lowercased value is NOT "0"
// or "false" (the idea is to make it easy trning things on and off when needed).
//
// IMPORTANT. This functionality depends on changes in System.Xaml. The info is
// exposed via modified XamlObjectEventArgs (3 new properties added). Because
// PresentationCore and PresentationFramework are serviced without System.Xaml
// it is possible that this code runs against "old" System.Xaml. In that case we
// need to turn this feature off. We should also use reflection to access 3 new
// properties. SourceBamlUri, ElementLineNumber and ElementLinePosition.
//
// NOTE that public API is exposed via VisualDiagnostics (centralized place).
//
using System.Reflection;
using System.Runtime.CompilerServices; // ConditionalWeakTable
using System.Xaml;
namespace System.Windows.Diagnostics
{
internal static class XamlSourceInfoHelper
{
public const string XamlSourceInfoEnvironmentVariable = "ENABLE_XAML_DIAGNOSTICS_SOURCE_INFO";
// Weak reference storage to map objects to their markup location. It is fast enough, e.g. it takes
// about 50ms to add 100K entries (small test app, Debug build, under debugger, dev laptop).
private static ConditionalWeakTable<object, XamlSourceInfo> s_sourceInfoTable; // no storage by default
// While ConditionalWeakTable is thread safe we need to make multiple calls in a thread safe manner.
private static readonly object s_lock = new object();
private static PropertyInfo s_sourceBamlUriProperty;
private static PropertyInfo s_elementLineNumberProperty;
private static PropertyInfo s_elementLinePositionProperty;
internal static bool IsXamlSourceInfoEnabled
{
get { return (s_sourceInfoTable != null); }
}
static XamlSourceInfoHelper()
{
InitializeEnableXamlSourceInfo(null);
}
// this method is (also) called via private reflection from test code
private static void InitializeEnableXamlSourceInfo(string value)
{
if (VisualDiagnostics.IsEnabled &&
VisualDiagnostics.IsEnvironmentVariableSet(value, XamlSourceInfoHelper.XamlSourceInfoEnvironmentVariable) &&
InitializeXamlObjectEventArgs())
{
s_sourceInfoTable = new ConditionalWeakTable<object, XamlSourceInfo>();
}
else
{
s_sourceInfoTable = null;
}
}
private static bool InitializeXamlObjectEventArgs()
{
// In case of a servicing scenario we might be dealing with old version of XamlObjectEventArgs
// which does not have 3 new properties: SourceBamlUri, ElementLineNumber and ElementLinePosition.
// We'll use reflection to access those properties. If they are not available then feature is disabled.
// WE SHOULD REMOVE REFLECTION based access and start using direct access once we are sure it is safe.
Type type = typeof(System.Xaml.XamlObjectEventArgs);
s_sourceBamlUriProperty = type.GetProperty("SourceBamlUri", BindingFlags.Public | BindingFlags.Instance);
s_elementLineNumberProperty = type.GetProperty("ElementLineNumber", BindingFlags.Public | BindingFlags.Instance);
s_elementLinePositionProperty = type.GetProperty("ElementLinePosition", BindingFlags.Public | BindingFlags.Instance);
// Make sure we've got all the properties and do sanity check
if (s_sourceBamlUriProperty == null || s_sourceBamlUriProperty.PropertyType != typeof(Uri) ||
s_elementLineNumberProperty == null || s_elementLineNumberProperty.PropertyType != typeof(int) ||
s_elementLinePositionProperty == null || s_elementLinePositionProperty.PropertyType != typeof(int))
{
// Old System.Xaml.
return false;
}
else
{
return true;
}
}
internal static void SetXamlSourceInfo(object obj, XamlObjectEventArgs args, Uri overrideSourceUri)
{
if (s_sourceInfoTable != null && args != null)
{
Uri sourceUri = overrideSourceUri ?? (Uri)s_sourceBamlUriProperty.GetValue(args);
int elementLineNumber = (int)s_elementLineNumberProperty.GetValue(args);
int elementLinePosition = (int)s_elementLinePositionProperty.GetValue(args);
SetXamlSourceInfo(obj, sourceUri, elementLineNumber, elementLinePosition);
}
}
// this method is (also) called via private reflection from test code
internal static void SetXamlSourceInfo(object obj, Uri sourceUri, int elementLineNumber, int elementLinePosition)
{
// Some BAML content - like system resources or Release builds - contains fake source info of line=0, pos=0. Ignore it.
if (s_sourceInfoTable != null && obj != null && !(elementLineNumber == 0 && elementLinePosition == 0))
{
// We should not store basic values like strings, enums, numbers, etc.
if (obj is string || obj.GetType().IsValueType)
{
return;
}
// This method can be called concurrently on multiple UI threads.
// Using lock to make sure remove+add are atomic.
lock (s_lock)
{
// We can see same object multiple times. Most common scenario is loading
// MyUserControl in context of Window1.xaml. When MyUserControl is constructed and
// thus loaded from MyUserControl.xaml its source info is set to MyUserControl.xaml.
// Then MyUserControl is added to Window1 visual tree and its source info is set
// to Window1.xaml, which is what we want. In other words use latest source.
s_sourceInfoTable.Remove(obj);
s_sourceInfoTable.Add(obj, new XamlSourceInfo(sourceUri, elementLineNumber, elementLinePosition));
}
}
}
internal static XamlSourceInfo GetXamlSourceInfo(object obj)
{
// ConditionalWeakTable is thread safe
XamlSourceInfo info = null;
if (s_sourceInfoTable != null && obj != null && s_sourceInfoTable.TryGetValue(obj, out info))
{
return info;
}
return null;
}
}
}
|