File: System\Windows\Documents\PageContent.cs
Web Access
Project: src\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.
// See the LICENSE file in the project root for more information.
 
using MS.Internal;
using MS.Internal.Documents;
using MS.Internal.Utility;
using System.Windows.Navigation;
using System.Windows.Markup;
using System.Windows.Threading;               // Dispatcher
using System.Collections.Specialized;
using System.ComponentModel;
using System.IO;
 
using MS.Utility;
 
//
// Description:
//      Implements the PageContent element
//
 
namespace System.Windows.Documents
{
    //=====================================================================
    /// <summary>
    /// PageContent is the class that references or directly hosts a page stream.
    /// Each page stream represents the content of a single page.  Each has its
    /// own isolated tree, ID space, property space, and resource table.
    ///
    /// The main function of PageContent is to load/parse the page content it
    /// references, and produce Visual tree that represents it page visual. A
    /// page visual tree always rooted at FixedPage.
    /// </summary>
    [ContentProperty("Child")]
    public sealed class PageContent : FrameworkElement, IAddChildInternal, IUriContext
    {
        //--------------------------------------------------------------------
        //
        // Connstructors
        //
        //---------------------------------------------------------------------
 
        #region Constructors
        /// <summary>
        ///     Default PageContent constructor
        /// </summary>
        /// <remarks>
        ///     Automatic determination of current Dispatcher. Use alternative constructor
        ///     that accepts a Dispatcher for best performance.
        /// </remarks>
        public PageContent() : base()
        {
            _Init();
        }
        #endregion Constructors
 
        //--------------------------------------------------------------------
        //
        // Public Methods
        //
        //---------------------------------------------------------------------
 
        #region Public Methods
        /// <summary>
        /// Synchonrously load and parse the page's tree
        /// </summary>
        /// <param name="forceReload">Force reloading the page tree instead of using cached value</param>
        /// <returns>The root FixedPage of the page's tree</returns>
        public FixedPage GetPageRoot(bool forceReload)
        {
#if DEBUG
            DocumentsTrace.FixedFormat.PageContent.Trace($"PageContent.GetPageRoot Source={(Source == null ? new Uri("", UriKind.RelativeOrAbsolute) : Source)}");
#endif
 
//             VerifyAccess();
            if (_asyncOp != null)
            {
                _asyncOp.Wait();
            }
 
            FixedPage p = null;
 
            if (!forceReload)
            {
                // If page was previously loaded, return the loaded page.
                p = _GetLoadedPage();
            }
 
            if (p == null)
            {
                p = _LoadPage();
            }
 
            return p;
        }
 
        /// <summary>
        /// Initiate an asynchoronus request to load the page tree.
        /// </summary>
        /// <param name="forceReload">Force reloading the page tree instead of using cached value</param>
        public void GetPageRootAsync(bool forceReload)
        {
#if DEBUG
            DocumentsTrace.FixedFormat.PageContent.Trace($"PageContent.GetPageRootAsync Source={(Source == null ? new Uri("", UriKind.RelativeOrAbsolute) : Source)}");
#endif
 
//             VerifyAccess();
 
            if (_asyncOp != null)
            {
                return;
            }
 
            FixedPage p = null;
            if (!forceReload)
            {
                p = _GetLoadedPage();
            }
 
 
            if (p != null)
            {
                _NotifyPageCompleted(p, null, false, null);
            }
            else
            {
                Dispatcher dispatcher = this.Dispatcher;
                Uri uriToLoad = _ResolveUri();
 
                if (uriToLoad != null || _child != null)
                {
                    _asyncOp = new PageContentAsyncResult(new AsyncCallback(_RequestPageCallback), null, dispatcher, uriToLoad, uriToLoad, _child);
                    _asyncOp.DispatcherOperation = dispatcher.BeginInvoke(DispatcherPriority.Normal, new DispatcherOperationCallback(_asyncOp.Dispatch), null);
                }
            }
        }
 
        /// <summary>
        /// Cancel outstanding async call
        /// </summary>
        public void GetPageRootAsyncCancel()
        {
#if DEBUG
            DocumentsTrace.FixedFormat.PageContent.Trace($"PageContent.GetPageRootAsyncCancel Source={(Source == null ? new Uri("", UriKind.RelativeOrAbsolute) : Source)}");
#endif
//             VerifyAccess();
            // Important: do not throw if no outstanding GetPageRootAsyncCall
            if (_asyncOp != null)
            {
                _asyncOp.Cancel();
                _asyncOp = null;
            }
        }
        #endregion Public Methods
 
        #region IAddChild
        ///<summary>
        /// Called to Add the object as a Child.
        ///</summary>
        /// <exception cref="ArgumentNullException">value is NULL.</exception>
        /// <exception cref="ArgumentException">value is not of type FixedPage.</exception>
        /// <exception cref="InvalidOperationException">A child already exists (a PageContent can have at most one child).</exception>
        ///<param name="value">
        /// Object to add as a child
        ///</param>
        /// <ExternalAPI/>
        void IAddChild.AddChild(Object value)
        {
//             VerifyAccess();
 
            ArgumentNullException.ThrowIfNull(value);
 
 
            FixedPage fp = value as FixedPage;
            if (fp == null)
            {
                throw new ArgumentException(SR.Format(SR.UnexpectedParameterType, value.GetType(), typeof(FixedPage)), "value");
            }
 
            if (_child != null)
            {
                throw new InvalidOperationException(SR.Format(SR.CanOnlyHaveOneChild, typeof(PageContent), value));
            }
 
            _pageRef = null;
            _child   = fp;
            LogicalTreeHelper.AddLogicalChild(this, _child);
        }
 
        ///<summary>
        /// Called when text appears under the tag in markup
        ///</summary>
        ///<param name="text">
        /// Text to Add to the Object
        ///</param>
        /// <ExternalAPI/>
        void IAddChild.AddText(string text)
        {
            XamlSerializerUtil.ThrowIfNonWhiteSpaceInAddText(text, this);
        }
        #endregion
 
 
        //--------------------------------------------------------------------
        //
        // Public Properties
        //
        //---------------------------------------------------------------------
 
        #region Public Properties
        /// <summary>
        /// Dynamic Property to reference an external page stream.
        /// </summary>
        public static readonly DependencyProperty SourceProperty =
                DependencyProperty.Register(
                        "Source",
                        typeof(Uri),
                        typeof(PageContent),
                        new FrameworkPropertyMetadata(
                                (Uri) null,
                                new PropertyChangedCallback(OnSourceChanged)));
 
        static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            PageContent p = (PageContent) d;
            p._pageRef = null;
        }
 
        /// <summary>
        /// Get/Set Source property that references an external page stream.
        /// </summary>
        public Uri Source
        {
            get { return (Uri) GetValue(SourceProperty); }
            set { SetValue(SourceProperty, value); }
        }
 
        /// <summary>
        /// Return LinkTargets. Note it is read only and return non-null collection to satify parser's requirement.
        /// </summary>
        public LinkTargetCollection LinkTargets
        {
            get
            {
                if (_linkTargets == null)
                {
                    _linkTargets = new LinkTargetCollection();
                }
                return _linkTargets;
            }
        }
 
        /// <summary>
        /// The fixed page content. This property will be available in the following conditions:
        /// 1) PageContent has an immediate FixedPage child in the markup
        /// 2) FixedPage has been set as a Child of this object using IAddChild
        /// On the other hand, if PageContent has a Source property for loading a FixedPage,
        /// the FixedPage will not be cached even it has been previously loaded
        /// In this case, the correct API method to use would be GetPageRoot or GetPageRootAsync
        /// </summary>
        [DefaultValue(null)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public FixedPage Child
        {
            get
            {
                return _child;
            }
            set
            {
                ArgumentNullException.ThrowIfNull(value);
 
                if (_child != null)
                {
                    throw new InvalidOperationException(SR.Format(SR.CanOnlyHaveOneChild, typeof(PageContent), value));
                }
 
                _pageRef = null;
                _child = value;
                LogicalTreeHelper.AddLogicalChild(this, _child);
            }
        }
 
        /// <summary>
        /// Prevent writing the property when using XamlPageContentSerializer
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool ShouldSerializeChild(XamlDesignerSerializationManager manager)
        {
            bool shouldSerialize = false;
            if (manager != null)
            {
                shouldSerialize = (manager.XmlWriter == null);
            }
            return shouldSerialize;
        }
 
        #endregion Public Properties
 
        #region IUriContext
        /// <summary>
        /// <see cref="IUriContext.BaseUri" />
        /// </summary>
        Uri IUriContext.BaseUri
        {
            get
            {
                return (Uri)GetValue(BaseUriHelper.BaseUriProperty);
            }
            set
            {
                SetValue(BaseUriHelper.BaseUriProperty, value);
            }
        }
        #endregion IUriContext
 
        //--------------------------------------------------------------------
        //
        // Public Events
        //
        //---------------------------------------------------------------------
 
        #region Public Event
        /// <summary>
        /// Event to notify GetPageRootAsync completed
        /// </summary>
        public event GetPageRootCompletedEventHandler GetPageRootCompleted;
        #endregion Public Event
 
 
        //--------------------------------------------------------------------
        //
        // Internal Methods
        //
        //---------------------------------------------------------------------
 
 
        #region Internal Methods
        /// <summary>
        /// Check to see if a page visual is created by this PageContent.
        /// </summary>
        /// <param name="pageVisual"></param>
        /// <returns></returns>
        internal bool IsOwnerOf(FixedPage pageVisual)
        {
//             VerifyAccess();
            ArgumentNullException.ThrowIfNull(pageVisual);
 
            if (_child == pageVisual)
            {
                return true;
            }
 
            if (_pageRef != null)
            {
                FixedPage c = (FixedPage)_pageRef.Target;
                if (c == pageVisual)
                {
                    return true;
                }
            }
            return false;
        }
 
        internal Stream GetPageStream()
        {
            Uri uriToLoad = _ResolveUri();
            Stream pageStream = null;
 
            if (uriToLoad != null)
            {
                pageStream = WpfWebRequestHelper.CreateRequestAndGetResponseStream(uriToLoad);
                if (pageStream == null)
                {
                    throw new ApplicationException(SR.PageContentNotFound);
                }
            }
 
            return pageStream;
        }
        #endregion Internal Methods
 
 
        //--------------------------------------------------------------------
        //
        // Internal Properties
        //
        //---------------------------------------------------------------------
 
        #region Internal Properties
        // return nested PageStream
        internal FixedPage PageStream
        {
            get
            {
                return _child;
            }
        }
 
        /// <summary>
        /// Search for the _LinkTargetCollection for named element in this fixedPage
        /// </summary>
        /// <param name="elementID"></param>
        /// <returns></returns>
        internal bool ContainsID(string elementID)
        {
            bool boolRet = false;
            foreach (LinkTarget item in LinkTargets)
            {
                if (elementID.Equals(item.Name))
                {
                    boolRet = true;
                    break;
                }
            }
 
            return boolRet;
        }
        /// <summary>
        /// Puts FixedPage in the logical tree if it is already loaded.
        /// </summary>
        protected internal override System.Collections.IEnumerator LogicalChildren
        {
            get
            {
                FixedPage[] children;
                FixedPage child = _child;
                if (child == null)
                {
                    child = _GetLoadedPage();
                }
 
                // Should this trigger a load?  We are not doing so, but if the
                // page has been loaded it has the page content as its logical
                // parent, so this must be consistent.
 
                if (child == null)
                {
                    children = Array.Empty<FixedPage>();
                }
                else
                {
                    children = new FixedPage[] { child };
                }
 
                return children.GetEnumerator();
            }
        }
 
        #endregion Internal Properties
 
        //--------------------------------------------------------------------
        //
        // private Properties
        //
        //---------------------------------------------------------------------
 
        #region Private Properties
        #endregion Private Properties
 
 
        //--------------------------------------------------------------------
        //
        // Private Methods
        //
        //---------------------------------------------------------------------
 
        #region Private Methods
        private void  _Init()
        {
            InheritanceBehavior = InheritanceBehavior.SkipToAppNow;
            _pendingStreams = new HybridDictionary();
        }
 
        private void _NotifyPageCompleted(FixedPage result, Exception error, bool cancelled, object userToken)
        {
            if (GetPageRootCompleted != null)
            {
                GetPageRootCompletedEventArgs e = new GetPageRootCompletedEventArgs(result, error, cancelled, userToken);
                GetPageRootCompleted(this, e);
            }
        }
 
        private Uri _ResolveUri()
        {
            Uri uriToNavigate = this.Source;
 
            if (uriToNavigate != null)
            {
                uriToNavigate = BindUriHelper.GetUriToNavigate(this, ((IUriContext)this).BaseUri, uriToNavigate);
            }
            return uriToNavigate;
        }
 
        private void _RequestPageCallback(IAsyncResult ar)
        {
            PageContentAsyncResult par = (PageContentAsyncResult)ar;
            if (par == _asyncOp && par.Result != null)
            {
                //par.Result.IsTreeSeparator = true;
                LogicalTreeHelper.AddLogicalChild(this, par.Result);
                _pageRef = new WeakReference(par.Result);
            }
 
            // Set outstanding async op to null to allow for reentrancy during callback.
            _asyncOp = null;
            _NotifyPageCompleted(par.Result, par.Exception, par.IsCancelled, par.AsyncState);
        }
 
 
        // sync load a page
        private FixedPage _LoadPage()
        {
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXGetPageBegin);
 
            FixedPage p = null;
            Stream pageStream;
            try
            {
                if (_child != null)
                {
                    p = _child;
                }
                else
                {
                    Uri uriToLoad = _ResolveUri();
 
                    if (uriToLoad != null)
                    {
                        _LoadPageImpl(((IUriContext)this).BaseUri, uriToLoad, out p, out pageStream);
 
                        if (p == null || p.IsInitialized)
                        {
                            pageStream.Close();
                        }
                        else
                        {
                            _pendingStreams.Add(p, pageStream);
                            p.Initialized += new EventHandler(_OnPaserFinished);
                        }
                    }
                }
 
                if (p != null)
                {
                    LogicalTreeHelper.AddLogicalChild(this, p);
                    _pageRef = new WeakReference(p);
                }
                else
                {
                    _pageRef = null;
                }
            }
            finally
            {
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXGetPageEnd);
            }
 
            return p;
        }
 
        private FixedPage _GetLoadedPage()
        {
            FixedPage p = null;
            if (_pageRef != null)
            {
                p = (FixedPage)_pageRef.Target;
            }
 
            return p;
        }
 
        private void _OnPaserFinished(object sender, EventArgs args)
        {
            if (_pendingStreams.Contains(sender))
            {
                Stream pageStream = (Stream)_pendingStreams[sender];
                pageStream.Close();
                _pendingStreams.Remove(sender);
            }
        }
 
        internal static void _LoadPageImpl(Uri baseUri, Uri uriToLoad, out FixedPage fixedPage, out Stream pageStream)
        {
            ContentType mimeType;
            pageStream = WpfWebRequestHelper.CreateRequestAndGetResponseStream(uriToLoad, out mimeType);
            object o = null;
            if (pageStream == null)
            {
                throw new ApplicationException(SR.PageContentNotFound);
            }
 
            ParserContext pc = new ParserContext();
            pc.BaseUri = uriToLoad;
 
            if (BindUriHelper.IsXamlMimeType(mimeType))
            {
                XpsValidatingLoader loader = new XpsValidatingLoader();
                o = loader.Load(pageStream, baseUri, pc, mimeType);
            }
            else if (MS.Internal.MimeTypeMapper.BamlMime.AreTypeAndSubTypeEqual(mimeType))
            {
                o = XamlReader.LoadBaml(pageStream, pc, null, true);
            }
            else
            {
                throw new ApplicationException(SR.PageContentUnsupportedMimeType);
            }
 
            if (o != null && !(o is FixedPage))
            {
                throw new ApplicationException(SR.Format(SR.PageContentUnsupportedPageType, o.GetType()));
            }
 
            fixedPage =  (FixedPage)o;
        }
 
        #endregion Private Methods
 
        //--------------------------------------------------------------------
        //
        // Private Fields
        //
        //---------------------------------------------------------------------
 
        #region Private Fields
        private WeakReference _pageRef;         // weak ref to page's root visual
        private FixedPage     _child;              // directly hosted page stream
        private PageContentAsyncResult  _asyncOp;
        private HybridDictionary  _pendingStreams;
        private LinkTargetCollection _linkTargets;
        #endregion Private Fields
    }
 
 
    /// <summary>
    /// EventArgs to return result from GetPageRootAsync
    /// </summary>
    public sealed class GetPageRootCompletedEventArgs : AsyncCompletedEventArgs
    {
        internal GetPageRootCompletedEventArgs(FixedPage page, Exception error, bool cancelled, object userToken)
            : base(error, cancelled, userToken)
        {
            _page = page;
        }
 
        /// <summary>
        /// The page tree resulted from GetPageRootAsync
        /// </summary>
        public FixedPage Result
        {
            get
            {
                this.RaiseExceptionIfNecessary();
                return _page;
            }
        }
 
        private FixedPage _page;
    }
 
    /// <summary>
    /// Delegate for GetPageRootAsync compeleted notification
    /// </summary>
    /// <param name="sender">The PageContent object that fired this notification</param>
    /// <param name="e">Event Arguments</param>
    public delegate void GetPageRootCompletedEventHandler(object sender, GetPageRootCompletedEventArgs e);
}