| File: System\Windows\Diagnostics\XamlSourceInfoHelper.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // // 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; } } }