| File: MS\Internal\DataStreams.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: // Holds the data for Avalon BindProducts in the journal // using System.Collections; using System.Collections.Specialized; using System.Formats.Nrbf; using System.IO; using System.Private.Windows.BinaryFormat; using System.Runtime.Serialization.Formatters.Binary; using System.Windows; using System.Windows.Controls; namespace MS.Internal.AppModel { #region SubStream struct [Serializable] internal struct SubStream { internal SubStream(string propertyName, byte[] dataBytes) { _propertyName = propertyName; _data = dataBytes; } internal string _propertyName; internal byte[] _data; } #endregion SubStream struct #region DataStreams class [Serializable] internal class DataStreams { internal DataStreams() { // Dummy constructor to keep FxCop Critical rules happy. } internal bool HasAnyData { get { return _subStreams != null && _subStreams.Count > 0 || _customJournaledObjects != null && _customJournaledObjects.Count > 0; } } private bool HasSubStreams(object key) { return _subStreams != null && _subStreams.Contains(key); } private ArrayList GetSubStreams(object key) { ArrayList subStreams = (ArrayList) _subStreams[key]; if (subStreams == null) { subStreams = new ArrayList(3); _subStreams[key] = subStreams; } return subStreams; } private delegate void NodeOperation(object node); /// <summary> /// Build an ArrayList of SubStreams where each SubStream represents one of the /// node's journalable DPs. /// </summary> /// <param name="element"></param> /// <returns>The ArrayList of SubStreams. May be null.</returns> private ArrayList SaveSubStreams(UIElement element) { ArrayList subStreams = null; #pragma warning disable 618 if ((element != null) && (element.PersistId != 0)) #pragma warning restore 618 { LocalValueEnumerator dpEnumerator = element.GetLocalValueEnumerator(); while (dpEnumerator.MoveNext()) { LocalValueEntry localValueEntry = (LocalValueEntry)dpEnumerator.Current; FrameworkPropertyMetadata metadata = localValueEntry.Property.GetMetadata(element.DependencyObjectType) as FrameworkPropertyMetadata; if (metadata == null) { continue; } // To be saved, a DP should have the correct metadata and NOT be an expression or data bound. // Since Bind inherits from Expression, the test for Expression will suffice. // NOTE: we do not journal expression. So we should let parser restore it in BamlRecordReader.SetDependencyValue. // Please see Windows OS bug # 1852349 for details. if (metadata.Journal && (!(localValueEntry.Value is Expression))) { // These properties should not be journaled. // There should be a better way to do this, maybe FrameworkPropertyMetadata.CausesNavigation? // Or maybe the Source property should be on INavigatorHost if (object.ReferenceEquals(localValueEntry.Property, Frame.SourceProperty)) continue; if (subStreams == null) { subStreams = new ArrayList(3); } object currentValue = element.GetValue(localValueEntry.Property); byte[] bytes = null; if ((currentValue != null) && !(currentValue is Uri)) { // Convert the value of the DP into a byte array MemoryStream byteStream = new MemoryStream(); bool success = false; try { success = BinaryFormatWriter.TryWriteFrameworkObject(byteStream, currentValue); } catch (Exception ex) when (!ex.IsCriticalException()) { // Being extra cautious here, but the Try method above should never throw in normal circumstances. Debug.Fail($"Unexpected exception writing binary formatted data. {ex.Message}"); } if(!success) { #pragma warning disable SYSLIB0011 // BinaryFormatter is obsolete this.Formatter.Serialize(byteStream, currentValue); #pragma warning restore SYSLIB0011 // BinaryFormatter is obsolete } bytes = byteStream.ToArray(); // Dispose the stream ((IDisposable)byteStream).Dispose( ); } // Save the byte array by the property name subStreams.Add(new SubStream(localValueEntry.Property.Name, bytes)); } } } return subStreams; } private void SaveState(object node) { UIElement element = node as UIElement; if (element == null) { return; } // Due to bug 1282529, PersistId can be null. Only XAML/BAML-loaded elements have it. // Besides for PageFunctions journaled by type, the PersistId check below is needed // because elements might have been added to the tree after loading from XAML/BAML. #pragma warning disable 618 int persistId = element.PersistId; #pragma warning restore 618 if (persistId != 0) { ArrayList subStreams = this.SaveSubStreams(element); if (subStreams != null) { // // If one element in the tree is replaced with a new element which is created // from a xaml/baml stream programatically, this new element and all its descendent nodes // would have another set of PersistId starting from 1, it would end up with two or more // elements in the same page share the same PersistId. We cannot guarantee to restore // journaldata for the newly added elements when doing the journaling navigation. // // So to do some level protection to avoid application crash, the code doesn't update the // journaldata for that element id if the data was set already. // // We cannot track whether the element with the specific PersistId is created from // the original xaml/baml stream, or is added later from another xaml/baml stream. // More efficient solution will be invented in next version. // if (!_subStreams.Contains(persistId)) { _subStreams[persistId] = subStreams; } } IJournalState customJournalingObject = node as IJournalState; if (customJournalingObject != null) { object customState = customJournalingObject.GetJournalState(JournalReason.NewContentNavigation); if (customState != null) { if (_customJournaledObjects == null) { _customJournaledObjects = new HybridDictionary(2); } // // Again, We cannot guarantee the PeristId of all elements in the same page are unique. // Some IJouralState aware node such as Frame, FlowDocumentPageViewer could be added // programatically after the page is created from baml stream, the new added node could also // be created from baml/xaml stream by Parser. // if (!_customJournaledObjects.Contains(persistId)) { _customJournaledObjects[persistId] = customState; } } } } } internal void PrepareForSerialization() { if (_customJournaledObjects != null) { foreach (DictionaryEntry entry in _customJournaledObjects) { CustomJournalStateInternal cjs = (CustomJournalStateInternal)entry.Value; cjs.PrepareForSerialization(); } } // Everything in _subStreams is already binary-serialized. } private void LoadSubStreams(UIElement element, ArrayList subStreams) { for (int subStreamIndex = 0; subStreamIndex < subStreams.Count; ++subStreamIndex) { SubStream subStream = (SubStream)subStreams[subStreamIndex]; // Restore the value of an individual DP DependencyProperty dp = DependencyProperty.FromName(subStream._propertyName, element.GetType()); // If the dp cannot be found it may mean that we navigated back to a loose file that has been changed. if (dp != null) { object newValue = null; if (subStream._data != null) { using MemoryStream dataStream = new(subStream._data); try { NrbfDecoder.Decode(dataStream, leaveOpen: true).TryGetFrameworkObject(out object val); newValue = val; } catch (Exception ex) when (!ex.IsCriticalException()) { // Being extra cautious here, but the Try method above should never throw in normal circumstances. Debug.Fail($"Unexpected exception reading binary formatted data. {ex.Message}"); } //Using Binary formatter if(newValue == null) { dataStream.Position = 0; #pragma warning disable SYSLIB0011 // BinaryFormatter is obsolete newValue = this.Formatter.Deserialize(dataStream); // CodeQL [SM04191] : This is a fallback deserialization to maintain backward compatibility for unsupported NRBF Data Types #pragma warning restore SYSLIB0011 // BinaryFormatter is obsolete } } element.SetValue(dp, newValue); } } } private void LoadState(object node) { UIElement element = node as UIElement; if (element == null) { return; } #pragma warning disable 618 int persistId = element.PersistId; #pragma warning restore 618 // Due to bug 1282529, PersistId can be null. Only XAML/BAML-loaded elements have it. if (persistId != 0) { if (this.HasSubStreams(persistId)) { // Get the properties to restore ArrayList properties = this.GetSubStreams(persistId); LoadSubStreams(element, properties); } if (_customJournaledObjects != null && _customJournaledObjects.Contains(persistId)) { CustomJournalStateInternal state = (CustomJournalStateInternal)_customJournaledObjects[persistId]; Debug.Assert(state != null); IJournalState customJournalingObject = node as IJournalState; // // For below two scenarios, JournalData cannot be restored successfully. For now, we just // simply ignore it and don't throw exception. // // A. After the tree was created from xaml/baml stream, some elements might be replaced // programatically with new elements which could be created from other xaml/baml by Parser. // // B. If the loose xaml file has been changed since the journal data was created // // customJournalingObject?.RestoreJournalState(state); } } } /// <summary> /// Walk the logical tree, and perform an operation on all nodes and children of nodes. /// This is slightly more complex than it might otherwise be because it makes no assumptions /// that the Logical Tree is strongly typed. It is the responsibility of the operation to make sure /// that it can handle the node it receives. /// </summary> /// <param name="node"></param> /// <param name="operation"></param> private void WalkLogicalTree(object node, NodeOperation operation) { if (node != null) { operation(node); } DependencyObject treeNode = node as DependencyObject; if (treeNode == null) { return; } IEnumerator e = LogicalTreeHelper.GetChildren(treeNode).GetEnumerator(); if (e == null) { return; } while (e.MoveNext()) { WalkLogicalTree(e.Current, operation); } } internal void Save(Object root) { if (_subStreams == null) { _subStreams = new HybridDictionary(3); } else { _subStreams.Clear(); } WalkLogicalTree(root, new NodeOperation(this.SaveState)); } internal void Load(Object root) { if (this.HasAnyData) { WalkLogicalTree(root, new NodeOperation(this.LoadState)); } } internal void Clear() { _subStreams = null; _customJournaledObjects = null; } #pragma warning disable SYSLIB0011 // Type or member is obsolete #region Private and internal fields and properties private BinaryFormatter Formatter { get { if (_formatter == null) { _formatter = new BinaryFormatter(); } return _formatter; } } [ThreadStatic] private static BinaryFormatter _formatter; private HybridDictionary _subStreams = new HybridDictionary(3); /// <summary> /// PersistId->CustomJournalStateInternal map /// <see cref="SaveState"/>. /// </summary> private HybridDictionary _customJournaledObjects; #endregion Private and internal fields and properties } #endregion DataStreams class #pragma warning restore SYSLIB0011 // Type or member is obsolete }