Code:
/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Framework / System / Windows / Navigation / NavigationService.cs / 1305600 / NavigationService.cs
//---------------------------------------------------------------------------- // // File: NavigationService.cs // // Description: Implements the Avalon basic Navigation unit class // // Created: 10/27/01 by mihaii // Modified: // 10/19/04 [....] Renamed from XamlContainer to NavigationService // 7/25/05 [....] Added CustomContentState journaling; journaling refactoring, especially around // fragment navigation // 9/02/05 [....] Introduced JournalEntryGroupState. Refactoring and bug-fixing around PageFunctions // 11/14/05 [....] "Island Frame" implementation. Now NavigationService talks to a // JournalNavigationScope instead of NavigationWindow and NavigationWindow.Journal. // // Copyright (C) 2001 by Microsoft Corporation. All rights reserved. // //--------------------------------------------------------------------------- using System; using System.Timers; using System.IO; using System.IO.Packaging; using System.Globalization; using System.Windows.Threading; using System.Collections; using System.ComponentModel; using System.Reflection; using System.Diagnostics; using System.Security; using System.Security.Permissions; using System.Runtime.Serialization.Formatters.Binary; using System.Net; using System.Net.Cache; using MS.Internal; using MS.Internal.Navigation; using MS.Internal.Utility; using MS.Internal.AppModel; using MS.Internal.Controls; using MS.Utility; using System.Windows; using System.Windows.Automation.Peers; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Media; using System.Windows.Markup; //In order to avoid generating warnings about unknown message numbers and //unknown pragmas when compiling your C# source code with the actual C# compiler, //you need to disable warnings 1634 and 1691. (Presharp Documentation) #pragma warning disable 1634, 1691 namespace System.Windows.Navigation { #region NavigationService Class ////// NavigationService provides the Navigation functionality /// /// All security sensitive classes should be sealed or protected with InheritanceDemand // NavigationService does implement INavigator, but it's not declared explicitly in order // to prevent inadvertently passing NavigationService instead of its navigator host // (NavigationWindow or Frame). public sealed class NavigationService : IContentContainer /*See comment above*/ { #region Constructors ////// Internal class used to host content and handles all navigations /// /// /// Parent navigator that uses and owns this NS. (It's either NavigationWindow or Frame.) /// internal NavigationService(INavigator nav) { INavigatorHost = nav; if (!(nav is NavigationWindow)) // NW has null GUID. GuidId = Guid.NewGuid(); } #endregion Constructors #region Private Methods private void ResetPendingNavigationState(NavigationStatus newState) { // If this container is done loading decrement the window's NavigationService bytes by the final amts of this container JournalNavigationScope jns = JournalScope; if (jns != null && jns.RootNavigationService != this) { // If there were two child frames loading simultaneously, then rootNavigationService will reflect // only the remaining child's progress now else this will reset window's totals to zero jns.RootNavigationService.BytesRead -= _bytesRead; jns.RootNavigationService.MaxBytes -= _maxBytes; } _navStatus = newState; _bytesRead = 0; _maxBytes = 0; #if DEBUG // We should only be replacing queue items that aren't already posted Debug.Assert(_navigateQueueItem == null || _navigateQueueItem.IsPosted == false); #endif _navigateQueueItem = null; _request = null; } // Navigate event fired by Hyperlink private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) { Debug.Assert(e != null, "Hyperlink fired Navigate event with null NavigateEventArgs"); e.Handled = true; string target = e.Target; Uri bpu = e.Uri; // If the Uri is absolute uri, we just take the uri. Otherwise we require sender to implement // IUriContext so we can resolve with its base uri. if ((bpu != null) && (bpu.IsAbsoluteUri == false)) { DependencyObject dobj = e.OriginalSource as DependencyObject; Debug.Assert(dobj != null, "RequestNavigateEventArgs.OriginalSource should be DependencyObject"); //This is usually a pack uri, with the path relative to the base of the application. // The app base is abstracted out to pack://application:,,,/ in the pack Uri. //This is set by the baml record reader but other implementors can choose to return any Uri. IUriContext uc = dobj as IUriContext; //Throw an exception if IUriContext is not implemented, any element can raise this event since it is public. if (uc == null) throw new Exception(SR.Get(SRID.MustImplementIUriContext, typeof(IUriContext))); bpu = BindUriHelper.GetUriToNavigate(dobj, uc.BaseUri, e.Uri); } INavigatorBase navigator = null; bool inSameThread = true; if (!String.IsNullOrEmpty(target)) { // // } // Specially handle the target as the root of this navigation window. // what special ID should we use this Navigator. // maybe we can use "Root" as the target name for this case. // The below code is for other case. // First check from the current NavigationService. It is needed for the island frame case. // Island frame can be inside a Window (instead of a NavWin). And JounralScope is not null only // after the initial navigation. // But it is still unsupported case: Two "island" frames within a non-NavigationWindow. // Then one can't target the other. navigator = this.FindTarget(target); // Try the current JournalNavigationScope before the entire window. if ((navigator == null) && (JournalScope != null)) { navigator = JournalScope.FindTarget(target); } if (navigator == null) { // We should at the very least check current window -if we have one- before we iterate rest of the windows NavigationWindow navWin = FindNavigationWindow(); if (navWin != null) { navigator = FindTargetInNavigationWindow(navWin, target); } // Didn't find it in the window, try the NavigationWindows in the WindowsCollection in the application if (navigator == null) { navigator = FindTargetInApplication(target); if (navigator != null) { inSameThread = (((DispatcherObject)navigator).CheckAccess() == true); } } } } else { navigator = INavigatorHost; } if (navigator != null) { if (inSameThread) { navigator.Navigate(bpu); } else { ((DispatcherObject)navigator).Dispatcher.BeginInvoke( DispatcherPriority.Send, (DispatcherOperationCallback)delegate(object unused) { return navigator.Navigate(bpu); }, null); } } else { if (Application.InBrowserHostedApp()) { LaunchResult launched = LaunchResult.NotLaunched; if (SecurityHelper.AreStringTypesEqual(bpu.Scheme, BaseUriHelper.PackAppBaseUri.Scheme)) { bpu = BaseUriHelper.ConvertPackUriToAbsoluteExternallyVisibleUri(bpu); } launched = AppSecurityManager.SafeLaunchBrowserOnlyIfPossible(CurrentSource, bpu, target, true /*IsTopLevelContainer*/); if (launched == LaunchResult.NotLaunched) { throw new System.Exception(SR.Get(SRID.FailToNavigateUsingHyperlinkTarget)); } } else { throw new System.ArgumentException(SR.Get(SRID.HyperLinkTargetNotFound)); } } } // Tests if two uris resolve to the same Uri. The Uri fragments are also // compared. Neither comparison is case sensitive. static private bool IsSameUri(Uri baseUri, Uri a, Uri b, bool withFragment) { if (object.ReferenceEquals(a, b)) // also handles both null { return true; } if (a == null || b == null) { return false; } Uri aResolved = BindUriHelper.GetResolvedUri(baseUri, a); Uri bResolved = BindUriHelper.GetResolvedUri(baseUri, b); bool isSame = aResolved.Equals(bResolved); if (isSame && withFragment) { isSame = isSame && (string.Compare(aResolved.Fragment, bResolved.Fragment, StringComparison.OrdinalIgnoreCase) == 0); } return isSame; } ////// Navigates to a fragment within the current page AND/OR replays a CustomContentState /// within the current page, and updates the journal. /// ////// Currently we do not strictly distinguish between fragment and CustomContentState /// navigations, which may not work well for all applications. CustomContentState is requested /// (but not required) and replayed for fragment navigations too. This will work for Mongoose, /// for example, but if an application needs to handle the two navigation types differently, /// it will have to use the FragmentNavigation callback, raise an internal flag, and possibly /// return null from its GetContentState() implementation or alter the JournalEntryName to /// reflect the original fragment location. /// private void NavigateToFragmentOrCustomContentState(Uri uri, object navState) { Debug.Assert(_bp != null, "NavigationService should not handle a nav from a hyperlink thats not in its hosted tree"); NavigateInfo navInfo = navState as NavigateInfo; JournalEntry destinationEntry = null; if (navInfo != null) { Debug.Assert(IsConsistent(navInfo)); destinationEntry = navInfo.JournalEntry; // null for new navigation } NavigationMode navMode = navInfo == null ? NavigationMode.New : navInfo.NavigationMode; // * This method should work with navMode=Refresh. // Root Viewer state is saved first because the fragment navigation can change view state // (BringIntoView()). CustomJournalStateInternal rootViewerState = GetRootViewerState(JournalReason.FragmentNavigation); string fragmentName = uri != null ? BindUriHelper.GetFragment(uri) : null; bool hasCustomContentState = destinationEntry != null && destinationEntry.CustomContentState != null; // Note: The assertion earlier implies that CustomContentState can be replayed only // for Back or Forward navigations. If this method is called for a New navigation, // it is fragment-only. // About the second parameter to NavigateToFragment(): // Fragment navigation may include storing/replaying CustomContentState. One special // case here is when doing journal navigation and the given URI doesn't include a // fragment name. Then we don't know whether the destinationEntry was originally created // as a result of fragment navigation or AddBackEntry(). Fragment re-navigation to the // base URI is supposed to scroll the content to the top. But for replaying // CustomContentState only, that may be undesirable. (If an application happens to require // this behavior, it can include the scroll position in the CustomContentState.) bool targetElementExists = NavigateToFragment(fragmentName, !hasCustomContentState); // Do not record a new [fragment] navigation if the address bar will not change. if (navMode == NavigationMode.Back || navMode == NavigationMode.Forward || (targetElementExists && !IsSameUri(null, _currentSource, uri, true /* with Fragment */))) { Debug.Assert(navMode != NavigationMode.Refresh); // because of !IsSameUri() above try { _rootViewerStateToSave = rootViewerState; UpdateJournal(navMode, JournalReason.FragmentNavigation, destinationEntry); } finally { _rootViewerStateToSave = null; } // Remember the new location and the original relative uri Uri resolvedUri = BindUriHelper.GetResolvedUri(_currentSource, uri); _currentSource = resolvedUri; _currentCleanSource = BindUriHelper.GetUriRelativeToPackAppBase(uri); } // Fire the Navigated event here since we're bypassing the normal navigation path // HandleNavigated has the logic to fire LoadComplete as needed. // It also replays CustomContentState. Debug.Assert(_navStatus == NavigationStatus.Navigating); _navStatus = NavigationStatus.Navigated; HandleNavigated(navState, false/*navigatedToNewContent*/); } ////// Attempt to find the object with the specified elementId in the visual tree, and scroll to it. /// The elementId is typically the fragment part of a URI (without the leading '#'). If an /// element with the correct ID can't be found, try the root of the tree. /// /// The id of the element to find and scroll to /// See note in NavigateToFragmentOrCustomContentState() ///True if the element was found and scrolled to or handled by the FragmentNavigation event. Otherwise returns false. private bool NavigateToFragment(string elementId, bool scrollToTopOnEmptyFragment) { // if (FireFragmentNavigation(elementId)) { return true; } DependencyObject targetElement = null; // Try to find the target element if (String.IsNullOrEmpty(elementId)) { if (!scrollToTopOnEmptyFragment) { return false; } // This is the case where we navigate from source#bookmark to source, so scroll the root element into view ScrollContentToTop(); return true; } targetElement = LogicalTreeHelper.FindLogicalNode((DependencyObject)_bp, elementId) as DependencyObject; // Try to bring the target element into view BringIntoView(targetElement); return targetElement != null; } private void ScrollContentToTop() { if (_bp != null) { // Supposedly temporary solution: handling the common case of a ScrollViewer inside a Page. // This special case has to come first because the wrong ScrollViewer (one enclosing a frame) // may respond if the ScrollBar.ScrollToTopCommand is tried first. FrameworkElement fe = _bp as FrameworkElement; if (fe != null) { IEnumerator children = fe.LogicalChildren; if (children != null && children.MoveNext()) { ScrollViewer sv = children.Current as ScrollViewer; if (sv != null) { sv.ScrollToTop(); return; } } } // This works when _bp is a ScrollViewer or there is one in the visual tree (provided // by a style). IInputElement elem = _bp as IInputElement; if (elem != null) { if (ScrollBar.ScrollToTopCommand.CanExecute(null, elem)) { ScrollBar.ScrollToTopCommand.Execute(null, elem); return; } } // Fallback. This works for the DocumentViewerBase derivatives. BringIntoView(_bp as DependencyObject); } } private static void BringIntoView(DependencyObject elem) { FrameworkElement fe = elem as FrameworkElement; if (fe != null) { fe.BringIntoView(); } else { FrameworkContentElement fce = elem as FrameworkContentElement; if (fce != null) { fce.BringIntoView(); } } } ////// ///property /// Can be null private JournalNavigationScope EnsureJournal() { if (_journalScope == null && _navigatorHost != null) { _journalScope = _navigatorHost.GetJournal(true/*do create*/); } // return _journalScope; } private bool IsParentedByBrowserWindow() { // We want to update browser related state only if // // 1. There's no window associated with this NavigationService (meaning that this NavigationService) // was created by Application) and if we're hosted in the Browser i.e BCBS is not null // // or // // 2. The window associated with this NavigationService is a RootBrowserWindow (meaning that we're // hosted in the browser and RBW is navigated // and // we are not in a Frame with its own Journal. // if (this.Application != null && this.Application.CheckAccess()) { if ((JournalScope != null && JournalScope.NavigatorHost is RootBrowserWindow) || (JournalScope == null && this.Application.BrowserCallbackServices != null)) { return true; } } return false; } bool IsConsistent(NavigateInfo navInfo) { return navInfo == null || navInfo.IsConsistent && (navInfo.JournalEntry == null || navInfo.JournalEntry.NavigationServiceId == _guidId); } private bool IsJournalNavigation(NavigateInfo navInfo) { return navInfo != null && (navInfo.NavigationMode == NavigationMode.Back || navInfo.NavigationMode == NavigationMode.Forward); } private CustomJournalStateInternal GetRootViewerState(JournalReason journalReason) { if (_navigatorHostImpl != null && !(_bp is Visual)) { Visual v = _navigatorHostImpl.FindRootViewer(); IJournalState ijs = v as IJournalState; if (ijs != null) { return ijs.GetJournalState(journalReason); } } return null; } private bool RestoreRootViewerState(CustomJournalStateInternal rvs) { Debug.Assert(!(_bp is Visual)); Visual v = _navigatorHostImpl.FindRootViewer(); if (v == null) return false; // Template may not be applied yet. IJournalState ijs = v as IJournalState; if (ijs != null) { ijs.RestoreJournalState(rvs); } //else: maybe type of viewer changed. Still returning true so that restoring state // is not reattempted in this case. return true; } ////// When it is top level navigation away from loose XAML (not the intial navigation to the loose xaml, /// nor a refresh), we want to delegate to the browser right away. There is no need to go through our /// navigation process, because no matter what content type (xaml, html...) it is trying to navigate to, /// we always let the browser handle it when it is top-level navigation from inside XamlViewer. /// /// V3 SP1 Optimization: /// We can avoid the cost of recycling the host process when: /// 1) The new content is from the same site-of-origin. This way there is no danger of cross-domain /// attacks made possible by poor cleanup. See SecurityNote below. /// 2) We can update the address bar with the new URL, or there is no address bar, which is when /// XamlViewer is hosted in an HTML frame. /// ////// This function may return false and we still end up delegating to the browser. This will be the /// case when the top-level navigation is to something other than XAML. GetObjectFromResponse() /// handles this case. /// ////// There is no check for same site-of-origin here. A call to SecurityHelper.CallerHasWebPermission( /// resolvedUri) could be added, but that would create an additional, redundant code path in /// navigation. Here's what happens when the new URI is outside the site-of-origin: /// - For `http://, CreateWebRequest() gets a SecurityException and delegates to the browser. /// - For file://, HandleGetResponse() gets a SecurityException from /// (File)WebRequest.EndGetResponse() and similarly delegates to the browser. /// private bool ShouldDelegateXamlViewerNavigationToBrowser(NavigateInfo navigateInfo, Uri resolvedUri) { bool shouldDelegate = false; if (BrowserInteropHelper.IsViewer) { Invariant.Assert(resolvedUri != null && resolvedUri.IsAbsoluteUri); shouldDelegate = !BrowserInteropHelper.IsInitialViewerNavigation && (navigateInfo == null || navigateInfo.NavigationMode != NavigationMode.Refresh) && IsTopLevelContainer && // except when we can update the address bar or we are in a frame: !(!BrowserInteropHelper.IsAvalonTopLevel || HasTravelLogIntegration); } return shouldDelegate; } ////// Critical: Calls IBCS.UpdateAddressBar(), which can be used for URL spoofing. /// Safe: The browser's address bar is updated only when XamlViewer navigates to another loose XAML /// file, with the URL of that file. (Also when doing fragment navigation within a document.) /// To prevent spoofing of _currentSource with a URL outside the site of origin, a web permission /// Demand is made. (It's okay for a site to show one page while the address bar is pointing to /// another from the same site. This is equivalent to using a frame that occupies the entire /// content area...) /// [SecurityCritical, SecurityTreatAsSafe] void UpdateAddressBarForLooseXaml() { if (BrowserInteropHelper.IsViewer && !BrowserInteropHelper.IsInitialViewerNavigation && IsTopLevelContainer) { Uri source = _currentSource; if (PackUriHelper.IsPackUri(source)) { source = BaseUriHelper.ConvertPackUriToAbsoluteExternallyVisibleUri(source); } Invariant.Assert(_navigatorHost != null && _navigatorHost == Application.MainWindow && source.IsAbsoluteUri && !PackUriHelper.IsPackUri(source)); SecurityHelper.DemandWebPermission(source); this.Application.BrowserCallbackServices.UpdateAddressBar(source.ToString()); } } #endregion Private Methods #region Internal Methods ////// /// ///static internal INavigatorBase FindTargetInApplication(string targetName) { // Application has two window collections. One for Application windows (windows // created on the same thread as the app) and the other for all other windows. // we will try to find target in all of these windows. if (Application.Current == null) return null; // WindowsInternal takes a lock to access the storage. We want to clone it and use the copy. // Otherwise, while we iterate over it some other thread could modify the collection. // Typically, there won't be a lot of windows in an App, so this should not be that costly // // Same argument goes for NonAppWindowsInternal.Clone() below // INavigatorBase navigator = FindTargetInWindowCollection(Application.Current.WindowsInternal.Clone(), targetName); // if we didn't find the target in one of the App windows, search for it in windows on // non app thread if (navigator == null) { navigator = FindTargetInWindowCollection(Application.Current.NonAppWindowsInternal.Clone(), targetName); } return navigator; } static private INavigatorBase FindTargetInWindowCollection(WindowCollection wc, string targetName) { INavigatorBase navigator = null; NavigationWindow nw = null; for (int i = 0; i < wc.Count; i++) { nw = wc[i] as NavigationWindow; if (nw != null) { // if we're on the same thread as that of nw then we can simple try to // find target in nw, else we need to find target on the nw's thread. // We do that below by using nw.Dispatcher.Invoke if (nw.CheckAccess() == true) { navigator = FindTargetInNavigationWindow(nw, targetName); } else { navigator = (INavigator)nw.Dispatcher.Invoke( DispatcherPriority.Send, (DispatcherOperationCallback)delegate(object unused) { return FindTargetInNavigationWindow(nw, targetName); }, null ); } if (navigator != null) { return navigator; } } } return null; } /// /// Find a navigator tree for the given navigator ID /// /// Navigation Window /// NavigatorId to search ///static private INavigatorBase FindTargetInNavigationWindow(NavigationWindow navigationWindow, string navigatorId) { if (navigationWindow != null) { return navigationWindow.NavigationService.FindTarget(navigatorId); } return null; } internal void InvalidateJournalNavigationScope() { // If there is a pending journal navigation (Back/Fwd), the JournalNavigationScope cannot // be changed. (If it is a _new_ navigation, we're OK; it will be recorded in the new // applicable journal.) // _navStatus or _navigateQueueItem are not checked here, because they are set only after // raising the Navigating event, while an event handler might cause journal ownership to change. if (_journalScope != null && _journalScope.Journal.HasUncommittedNavigation) throw new InvalidOperationException(SR.Get(SRID.InvalidOperation_CantChangeJournalOwnership)); _journalScope = null; for (int i = ChildNavigationServices.Count - 1; i >= 0; i--) { ((NavigationService)ChildNavigationServices[i]).InvalidateJournalNavigationScope(); } } internal void OnParentNavigationServiceChanged() { NavigationService oldParent = _parentNavigationService; NavigationService newParent = ((DependencyObject)INavigatorHost).GetValue(NavigationServiceProperty) as NavigationService; if (newParent == oldParent) return; if (oldParent != null) { // Remove from old parent's list oldParent.RemoveChild(this); } if (newParent != null) { // Add to new parent's list newParent.AddChild(this); Debug.Assert(_parentNavigationService == newParent); } } internal void AddChild(NavigationService ncChild) { // This can happen when a Frame is navigated to the page containing it (object navigation). if (ncChild == this) throw new Exception(SR.Get(SRID.LoopDetected, _currentCleanSource)); Invariant.Assert(ncChild.ParentNavigationService == null); Invariant.Assert(ncChild.JournalScope == null || ncChild.IsJournalLevelContainer, "Parentless NavigationService has a reference to a JournalNavigationScope its host navigator doesn't own."); ChildNavigationServices.Add(ncChild); ncChild._parentNavigationService = this; if (JournalScope != null) { // The view may need to be changed if NavigationContainers came or went JournalScope.Journal.UpdateView(); } // If parent's navigation was stopped, stop pending navigations in the child as well if (this.NavStatus == NavigationStatus.Stopped) { ncChild.INavigatorHost.StopLoading(); return; } // Add child to pendinglist if both child and parent are navigating if ((ncChild.NavStatus != NavigationStatus.Idle && ncChild.NavStatus != NavigationStatus.Stopped) && (this.NavStatus != NavigationStatus.Idle && this.NavStatus != NavigationStatus.Stopped)) { PendingNavigationList.Add(ncChild); } } internal void RemoveChild(NavigationService ncChild) { Debug.Assert(ChildNavigationServices.Contains(ncChild), "Child NavigationService must already exist"); // Remove won't cause an exception if not in the arraylist ChildNavigationServices.Remove(ncChild); ncChild._parentNavigationService = null; if (!ncChild.IsJournalLevelContainer) { ncChild.InvalidateJournalNavigationScope(); } if (JournalScope != null) { // The view may need to be changed if NavigationContainers came or went JournalScope.Journal.UpdateView(); } // if (PendingNavigationList.Contains(ncChild)) { PendingNavigationList.Remove(ncChild); // Fire LoadCompleted if appropriate - i.e. if this was the final child we were waiting for HandleLoadCompleted(null); } } // internal NavigationService FindTarget(Guid navigationServiceId) { if (this.GuidId == navigationServiceId) return this; NavigationService result = null; foreach (NavigationService ns in ChildNavigationServices) { // Possible optimization: Don't recurse into a Frame with its own journal. result = ns.FindTarget(navigationServiceId); if (result != null) return result; } return null; } /// /// Find the node with the given Navigator Name in this /// subtree rooted by this node. It is possible to have more than one /// node in the tree with the same Name, then we return the first one found /// /// the navigator Name to search ///Navigator which matches the given id internal INavigatorBase FindTarget(string name) { FrameworkElement fe = INavigatorHost as FrameworkElement; Debug.Assert(fe != null, "INavigatorHost needs to be FrameworkElement"); if (String.Compare(name, fe.Name, StringComparison.OrdinalIgnoreCase) == 0) { return INavigatorHost; } INavigatorBase target = null; foreach (NavigationService xcChild in ChildNavigationServices) { target = xcChild.FindTarget(name); if (target != null) return target; } return target; } // JournalEntry.KeepAlive value map to journal method // // JournalEntry.KeepAlive true false // Navigation by Uri by KeepAlive by Uri // Navigation by Object by KeepAlive by KeepAlive // Navigation to PageFunction by KeepAlive by class // // Additional note: // 1. Return true for KeepAlive. // 2. When return false, it means by Uri for uri nav; by class for pagefunction. // 3. For object nav, always return true (KeepAlive). // 4. For null object/uri nav, always return true (KeepAlive). internal bool IsContentKeepAlive() { bool keepAlive = true; DependencyObject o = _bp as DependencyObject; // Anything with null Content or when Content is not DO is KeepAlive, since we can't get an attached // DP from a null reference. if (o != null) { // Get the content from the attached DP keepAlive = JournalEntry.GetKeepAlive(o); if (keepAlive == false) { PageFunctionBase pf = o as PageFunctionBase; // For object navigation, always return true (KeepAlive). bool noUriToReloadFrom = !CanReloadFromUri; if (pf == null && noUriToReloadFrom) { keepAlive = true; } } } return keepAlive; } #endregion Internal Methods // // Set Uri to root element's BaseUri DependencyProperty. // private void SetBaseUri(DependencyObject dobj, Uri fullUri) { Invariant.Assert((dobj != null) && (! dobj.IsSealed)); Uri curBaseUri; // If the BaseUri was set already, don't bother to reset it. // This could happen for navigating to element, and /or KeepAlive. curBaseUri = (Uri)(dobj.GetValue(BaseUriHelper.BaseUriProperty)); if (curBaseUri == null && fullUri != null) { // // Get BaseUri from current Uri, and set it into root element of the new tree. // Uri baseUri = fullUri; dobj.SetValue(BaseUriHelper.BaseUriProperty, baseUri); } } private bool UnhookOldTree(Object oldTree) { //------------------------------------------------------------------------------- // // Step 1: Clear NavigationService property // DependencyObject dobj = oldTree as DependencyObject; // Currently there is no public API to seal a DO other than Freezable. In other // words, you can only seal Freezable. You cannot seal Visual, UIElement, FrameworkElement. // Since we enable navigation to any element, we should not crash when the object is sealed. if ((dobj != null) && (! dobj.IsSealed)) { dobj.SetValue(NavigationServiceProperty, null); } // //------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- // // Step 2: Deal with Focus issues // // 1. Make sure that we remove keyboard focus from the old tree. // 2. The mouse will keep its reference until it detects it needs to re-hitttest. // An example of such a case is when layout happens. Since the new navigation will cause // a layout, we don't need to do anything specifically here. // // IInputElement.IsKeyboardFocusWithin works across subtrees as well so don't have to drill down subframes explicitly IInputElement iie = oldTree as IInputElement; if ((iie != null) && iie.IsKeyboardFocusWithin) { // We will need to set FocusedElement to null before setting Keyboard device focus to null. // The behavior for setting Keyboard device focus to null is setting to the root visual (e.g, NavWin); // the root element will then delegate it to the FocusedElement. If we do not set FocusedElement to null first, // Keyboard device will not set the focus to NavWin, but to the FocusedElement. // Ideally we should not need to do this. When a tree is removed focusedelement should be updated to null, keyboard device // should set the focus to root automatically. However Hyperlink does not have IsVisible property that heyboard // device checks to updated the focus. We will have to work around this issue. if (dobj != null && JournalScope != null) { DependencyObject focusScope = (DependencyObject)INavigatorHost; // If the NavigationHost is a focus scope, it is able to clear the FocusedElement. // However, when it is not, we need to get the closest focus scope and clear the FocusedElement. if (!((bool) focusScope.GetValue(FocusManager.IsFocusScopeProperty))) { focusScope = FocusManager.GetFocusScope(focusScope); } FocusManager.SetFocusedElement(focusScope, null); } Keyboard.PrimaryDevice.Focus(null); } // //------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- // // Step 3: Deal with PageFunction issues // // Detach the Finish handler so we don't hold a reference to the PageFunction. PageFunctionBase currentPF = oldTree as PageFunctionBase; if (currentPF != null) { currentPF.FinishHandler = null; } // // Dispose the old tree here. TEMP until Bug 864908 is fixed // if the root is a PageFunction whose KeepAlive is set to TRUE, or a page // with JournalMode=KeepAlive, we should not dispose the old tree. bool canDispose = true; if (IsContentKeepAlive()) { canDispose = false; } // //-------------------------------------------------------------------------------- return canDispose; } ///False, if the navigation is canceled. This can currently happen only when /// a PageFunction returns to a non-PF parent page, and the Return event handler starts a /// new navigation. This case should be handled consistently with HandleFinish(). /// private bool HookupNewTree(Object newTree, NavigateInfo navInfo, Uri newUri) { Debug.Assert(_navigateQueueItem == null && _navStatus == NavigationStatus.Navigated); // Restore the page state if (newTree != null && IsJournalNavigation(navInfo)) { navInfo.JournalEntry.RestoreState(newTree); // Note: When a PageFunction is being resumed because its child finished, RestoreState() // is called earlier. Because it clears the JournalDataStreams, the call here will do // nothing. // Note: journalEntry.CustomContentState.Replay() is called from HandleNavigated(). } //------------------------------------------------------------------------------- // // Step 1: Do PageFunction related stuff // This step is intentionally put as the first for event handler exception continuality: // if an exception occurs in the Return event handler, we would maintain a clean state. PageFunctionReturnInfo pfReturnInfo = navInfo as PageFunctionReturnInfo; // This will be non-null IFF a PageFunction with a non-PageFunction parent has finished. // Then navInfo.NavigationMode may be Back or New. // (New iff finishingChildPageFunction.RemoveFromJournal==false). PageFunctionBase finishingChildPageFunction = (pfReturnInfo != null) ? pfReturnInfo.FinishingChildPageFunction : null; Debug.Assert(finishingChildPageFunction == null || !IsPageFunction(newTree) && (finishingChildPageFunction.RemoveFromJournal && navInfo.NavigationMode == NavigationMode.Back || !finishingChildPageFunction.RemoveFromJournal && navInfo.NavigationMode == NavigationMode.New)); // Reattach the Return Event handler and fire the child PageFunction's Return event // if we are about to switch to the non-PageFunction parent of a PageFunction that // has just finished if (finishingChildPageFunction != null) { object returnEventArgs = (pfReturnInfo != null) ? pfReturnInfo.ReturnEventArgs : null; if (newTree != null) { FireChildPageFunctionReturnEvent(newTree, finishingChildPageFunction, returnEventArgs); if (_navigateQueueItem != null) { // Return event handler should not be left attached. Debug.Assert(finishingChildPageFunction._Return == null); if (pfReturnInfo.JournalEntry != null) { pfReturnInfo.JournalEntry.SaveState(newTree); } return false; } } // else // { // } // Note this special case: finishingChildPageFunction=null, but Content is PageFunctionBase. // This happens when navigating to a PF and then doing GoBack. Then the special // OnReturn/OnFinish PF handling is not done. if (IsPageFunction(newTree)) { // Attach the handler to the new one so we know when it Finishes SetupPageFunctionHandlers(newTree); // If a page function is started without attaching a Return event handler to it, // it doesn't know which parent page to return to. So, set it here in this case. // (See also PageFunctionBase._AddEventHandler().) if ((navInfo == null || navInfo.NavigationMode == NavigationMode.New) && !_doNotJournalCurrentContent) // the current PF may have been RemoveFromJournal'ed { Debug.Assert(pfReturnInfo == null); PageFunctionBase pf = (PageFunctionBase)newTree; // pf._Resume=true when a PF returns and recording a new navigation for the parent PF if (!pf._Resume && pf.ParentPageFunctionId == Guid.Empty && _bp is PageFunctionBase) { pf.ParentPageFunctionId = ((PageFunctionBase)_bp).PageFunctionId; Debug.Assert(pf.ParentPageFunctionId != Guid.Empty); } } } // //-------------------------------------------------------------------------------- //------------------------------------------------------------------------------- // // Step 2: Set NavigationService property and WebBrowser // DependencyObject dobj = newTree as DependencyObject; if ((dobj != null) && (! dobj.IsSealed)) { // Note: setting NavigationService has a non-obvious side effect - // if dobj has any data-bound properties that use ElementName binding, // the name will be resolved in the "inner scope", not the "outer // scope". (Bug 1765041) dobj.SetValue(NavigationServiceProperty, this); // Set BaseUriHelper.BaseUriProperty. // Special case: When returning to a Source-less element tree in which fragment // navigation was done, newUri will be just "#fragment". Don't set it then. if (newUri != null && !BindUriHelper.StartWithFragment(newUri)) { SetBaseUri(dobj, newUri); } } _webBrowser = newTree as WebBrowser; // //------------------------------------------------------------------------------- return true; } ///whether to continue with committing the navigation to the new content private bool OnBeforeSwitchContent(Object newBP, NavigateInfo navInfo, Uri newUri) { Debug.Assert(IsConsistent(navInfo)); #if DEBUG_CLR_MEM bool clrTracingEnabled = false; if (CLRProfilerControl.ProcessIsUnderCLRProfiler && (CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Verbose)) { clrTracingEnabled = true; ++_navigationCLRPass; CLRProfilerControl.CLRLogWriteLine("Begin_OnBeforeSwitchContent_{0}", _navigationCLRPass); } #endif // DEBUG_CLR_MEM // The order of those actions are: // 1. Config the new tree, which includes two steps: PageFunction related stuff (where Child PageFunction's Return event is fired) // and setting NavigationServiceProperty to this. // 2. Journal is updated with current page // 3. Clean up the old tree, which includes three steps: setting NavigationServiceProperty to null, setting focus to null, and // PageFunction related stuff. // 4. Dispose the old tree if it can be disposed. // We intentionally fires the PageFunction Return event at the beginning for exception continuality: if an exception occurs in // the event handler, we would maintain a clean state. if (newBP != null && !HookupNewTree(newBP, navInfo, newUri)) { Debug.Assert(!JournalScope.Journal.HasUncommittedNavigation); return false; } Debug.Assert(_navigateQueueItem == null); // Workaround for the reentrance problem from browser (bug 128689). // Call into browser before we update journal. If there is another navigation waiting, e.g, // user starts a new navigation using the browser back/forward button, it will // re-enter with this call. We can detect whether a new navigation has started by checking // _navigateQueueItem. The goal is to check for reentrance before we update journal. It should // be safe to cancel the current navigation at this point (before any journal changes). if (HasTravelLogIntegration) { DispatchPendingCallFromBrowser(); if (_navigateQueueItem != null) { return false; } } if (navInfo == null) { UpdateJournal(NavigationMode.New, JournalReason.NewContentNavigation, null); } else if (navInfo.NavigationMode != NavigationMode.Refresh) { UpdateJournal(navInfo.NavigationMode, JournalReason.NewContentNavigation, navInfo.JournalEntry); } // Check for reentrance again before we proceed. UpdateJournal calls CallUpdateTravelLog which calls // into browser that can cause a new navigation to reenter. // if (_navigateQueueItem != null) { return false; } bool canDispose = UnhookOldTree(_bp); #if DEBUG_CLR_MEM if (clrTracingEnabled && CLRProfilerControl.CLRLoggingLevel >= CLRProfilerControl.CLRLogState.Verbose) { CLRProfilerControl.CLRLogWriteLine("End_OnBeforeSwitchContent_{0}", _navigationCLRPass); } #endif // DEBUG_CLR_MEM // // Dispose the old tree after all the required work is done. // if (canDispose) { DisposeTreeQueueItem disposeItem = new DisposeTreeQueueItem(_bp); Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, new DispatcherOperationCallback(disposeItem.Dispatch), null); } return true; } // Allow new navigations from browser to re-enter with this call. ////// Critical - calls back to browser to get the browser top. /// TreatAsSafe - this value isn't returned or stored. /// [SecurityCritical, SecurityTreatAsSafe] private void DispatchPendingCallFromBrowser() { BrowserInteropHelper.HostBrowser.GetTop(); } ////// Called when style is actually applied. /// internal void VisualTreeAvailable(Visual v) { if (!ReferenceEquals(v, _oldRootVisual)) { if (_oldRootVisual != null) { // Step 1: Remove the inherited NavigationService property // This will cause a property invalidation and sub-frames will remove themselves from the parent's list // That will cause a Journal view update so back/fwd state reflects the state of the new tree _oldRootVisual.SetValue(NavigationServiceProperty, null); } if (v != null) { // Step 1: Set the inherited NavigationService property // This will cause a property invalidation and sub-frames will remove themselves from the parent's list // That will cause a Journal view update so back/fwd state reflects the state of the new tree // Note: setting NavigationService has a non-obvious side effect - // if v has any data-bound properties that use ElementName binding, // the name will be resolved in the "inner scope", not the "outer // scope". (Bug 1765041) v.SetValue(NavigationServiceProperty, this); } _oldRootVisual = v; } } #region IContentContainer Implementation ////// The callback that happens when the bind product corresponding to a /// URI has been created. /// /// MIME type from which product was created /// Content created. /// Absolute URI of content, or null /// void IContentContainer.OnContentReady(ContentType contentType, Object bp, Uri bpu, Object navState) { Invariant.Assert(bpu == null || bpu.IsAbsoluteUri, "Content URI must be absolute."); if (IsDisposed) { return; } // If an invalid root element is passed to Navigation Service, throw exception here. if (IsValidRootElement(bp) == false) { throw new InvalidOperationException(SR.Get(SRID.WrongNavigateRootElement, bp.ToString())); } /* */ ResetPendingNavigationState(NavigationStatus.Navigated); NavigateInfo navInfo = navState as NavigateInfo; NavigationMode navMode = navInfo == null ? NavigationMode.New : navInfo.NavigationMode; Debug.Assert(bpu == null || navInfo == null || navInfo.Source == null || IsSameUri(null, navInfo.Source, bpu, false /* withFragment */), "Source in OnContentReady does not match source in NavigateInfo"); if (bpu == null) { bpu = (navInfo == null) ? null : navInfo.Source; } Uri bpuClean = BindUriHelper.GetUriRelativeToPackAppBase(bpu); // This gives the Application a chance to see if this bind needs an AppWindow. // This will happen if this is the first tree we're loading and it doesn't // have a toplevel Window element. if (PreBPReady != null) { // ok to pass resolved Uri here because this is internal BPReadyEventArgs args = new BPReadyEventArgs(bp, bpu); PreBPReady(this, args); if (args.Cancel) { _navStatus = NavigationStatus.Idle; return; } } bool objectRefresh = false; if (object.ReferenceEquals(bp, _bp)) { Debug.Assert(navMode == NavigationMode.Refresh, "OnContentReady() should not be called with the same object except for Refresh."); objectRefresh = true; // Note: The converse is not true: When refreshing from a URI, bp will be a different object. // To force full refresh, the Content object is detached from the tree and reattached. // (Just invalidating layout would not cause ContentRendered to be raised.) _bp = null; if (BPReady != null) { BPReady(this, new BPReadyEventArgs(null, null)); } } else { // send resolved Uri here because OnBeforeSwitchContent sets it as the new base uri if (!OnBeforeSwitchContent(bp, navInfo, bpu)) { Debug.Assert(!JournalScope.Journal.HasUncommittedNavigation); return; } // On Refresh, keep the current ContentId. On journal navigation, restore it from the // journal entry instead of assigning a new one. This will ensure that fragment navigation // to other entries associated with the same content will still work properly. // (See Navigate(Uri, object).) if (navMode != NavigationMode.Refresh) { if (navInfo == null || navInfo.JournalEntry == null) // new navigation? { _contentId++; // Note: this is done even when bp==null. _journalEntryGroupState = null; // start anew } else { Debug.Assert(navMode == NavigationMode.Back || navMode == NavigationMode.Forward); _contentId = navInfo.JournalEntry.ContentId; Debug.Assert(_contentId != 0); // The JournalEntryGroupState object from the JE must be reused because other JEs // may have references to it. Debug.Assert(_journalEntryGroupState == null || _journalEntryGroupState.ContentId != _contentId); // because _bp != bp _journalEntryGroupState = navInfo.JournalEntry.JEGroupState; } // Set the source to the original source _currentSource = bpu; _currentCleanSource = bpuClean; } } _bp = bp; if (BPReady != null) { BPReady(this, new BPReadyEventArgs(_bp, bpu)); } // This will fire Navigated event and LoadCompleted event if all sub-loads are done HandleNavigated(navState, !objectRefresh/*navigatedToNewContent*/); } //// Function that gets called each time number of bytes equal to // bytesInterval is read // // Uri for which the progress event is being fired // Bytes Read // Max Bytes void IContentContainer.OnNavigationProgress(Uri sourceUri, long bytesRead, long maxBytes) { if (IsDisposed) { return; } // LoadXaml/LoadBaml cannot be aborted when the loading is not async. // The currrent navigation could have been cancelled by the application // in the NavigationProgress event handler. We should not raise the NavigationProgress event // when the uri we start with is not the same as the current one that we are navigating to. if (!sourceUri.Equals(Source)) { return; } NavigationService rootNavigationService = null; // Fire with cumulative totals at the top level container also unless this is the top level one. if (JournalScope != null && JournalScope.RootNavigationService != this) { rootNavigationService = JournalScope.RootNavigationService; // Update cumulative totals on the Window. Do this before we update current totals rootNavigationService.BytesRead += bytesRead - _bytesRead; rootNavigationService.MaxBytes += maxBytes - _maxBytes; } // We get cumulative bytesRead and maxBytes from Loader. maxBytes -may- get // updated dynamically if ContentLength was not known beforehand // When bytesRead == maxBytes, then we know that the download is done _bytesRead = bytesRead; _maxBytes = maxBytes; // FireNavigationProgress for this container, this will also fire on the application // with this container's progress bytes FireNavigationProgress(sourceUri); // Fire with cumulative totals at the top level container also unless this is the top level one. if (rootNavigationService == null) return; // Since we are using rootUri, fire with root's INavigatorHost rootNavigationService.FireNavigationProgress(sourceUri); // If this navigation gets Stopped or finishes completely, this containers cumulative totals // will get decremented from the root container in ResetPendingNavigationState() } void IContentContainer.OnStreamClosed(Uri sourceUri) { // LoadXaml/LoadBaml cannot be aborted when the loading is not async. // The currrent navigation could have been cancelled by the application // in the NavigationProgress event handler. We should not raise the LoadCompleted event // when the uri we start with is not the same as the current one that we are navigating to. if (!sourceUri.Equals(Source)) { return; } // Cannot close the WebResponse here because we hand out the response in the Navigated and LoadCompleted event args. // Have to wait to close it until then. The stream has been closed though. // If it was async parsing, it is finished when we get this call. _asyncObjectConverter = null; HandleLoadCompleted(null); } #endregion IContentContainer Implementation # region public method and property ////// Attached inherited DependencyProperty. It gives an element the NavigationService of the navigation container it's in. /// internal static readonly DependencyProperty NavigationServiceProperty = DependencyProperty.RegisterAttached( "NavigationService", typeof(NavigationService), typeof(NavigationService), new FrameworkPropertyMetadata( (NavigationService)null, FrameworkPropertyMetadataOptions.Inherits)); ////// Gets NavigationService of the navigation container the given dependencyObject is in. /// /// ///public static NavigationService GetNavigationService(DependencyObject dependencyObject) { if (dependencyObject == null) { throw new ArgumentNullException("dependencyObject"); } return dependencyObject.GetValue(NavigationServiceProperty) as NavigationService; } #region INavigator Implementation // // Uri INavigator.Source // /// /// Source Uri /// ///public Uri Source { get { if (IsDisposed) { return null; } if (_recursiveNavigateList.Count > 0) { // If we are in the middle of a recursive Navigate call (could happen if Navigating // event handler called Navigate), then return the Uri from the deepest callstack return BindUriHelper.GetUriRelativeToPackAppBase((_recursiveNavigateList[_recursiveNavigateList.Count - 1] as NavigateQueueItem).Source); } else if (_navigateQueueItem != null) { // Else return the Uri from the queued item (could still be waiting to be posted // or in progress) return BindUriHelper.GetUriRelativeToPackAppBase(_navigateQueueItem.Source); } else { // Return the one and only return _currentCleanSource; } } set { // IsDisposed is checked in Navigate() this.Navigate(value); } } // // Uri INavigator.CurrentSource // /// /// Current Source Uri /// ///public Uri CurrentSource { get { if (IsDisposed) { return null; } return _currentCleanSource; } } // // UIElement INavigator.Content // /// /// Current Content property /// ///public Object Content { get { if (IsDisposed) { return null; } return _bp; } set { // IsDisposed is checked in Navigate() this.Navigate(value); } } /// /// Adds a new journal entry to NavigationWindow's back history. /// /// The custom content state (or view state) to be encapsulated in the /// journal entry. If null, IProvideCustomContentState.GetContentState() will be called on /// the NavigationWindow.Content or Frame.Content object. /// public void AddBackEntry(CustomContentState state) { if (IsDisposed) { return; } if (_bp == null) throw new InvalidOperationException(SR.Get(SRID.InvalidOperation_AddBackEntryNoContent)); _customContentStateToSave = state; JournalEntry je = UpdateJournal(NavigationMode.New, JournalReason.AddBackEntry, null); // Controls state is not saved by design (saveContent=false). If client applications // require it to be synchronized with the CustomContentState, they can explicitly // include it. _customContentStateToSave = null; // Since state=null is allowed on input, make sure we get an object either via the // IProvideCustomContentState interface or from a Navigating event handler. // Otherwise it doesn't make sense to add a journal entry. if (je != null && je.CustomContentState == null) { RemoveBackEntry(); throw new InvalidOperationException( SR.Get(SRID.InvalidOperation_MustImplementIPCCSOrHandleNavigating, _bp != null ? _bp.GetType().ToString() : "null")); } } ////// Remove the first JournalEntry from NavigationWindow's back history /// public JournalEntry RemoveBackEntry() { if (IsDisposed) { return null; } if (JournalScope == null) return null; //(Normally, no exception is thrown if there is no back entry.) return JournalScope.RemoveBackEntry(); } // // bool INavigator.Navigate(Uri source) // ////// Navigate to source /// ///public bool Navigate(Uri source) { return this.Navigate(source, null, false, false); } // // bool INavigator.Navigate(UIElement bp) // /// /// Navigate to content tree. /// ///public bool Navigate(Object root) { return this.Navigate(root, null); } /// /// Navigate to the source. Null source results in clearing existing content /// ///returns bool to indicate if a navigation was started i.e. Navigating event was not cancelled public bool Navigate(Uri source, Object navigationState) { return this.Navigate(source, navigationState, false, false); } ////// Navigate to the source. Null source results in clearing existing content /// ///returns bool to indicate if a navigation was started i.e. Navigating event was not cancelled public bool Navigate(Uri source, Object navigationState, bool sandboxExternalContent) { return Navigate(source, navigationState, sandboxExternalContent, false); } ////// Navigate to the source. Null source results in clearing existing content /// ///returns bool to indicate if a navigation was started i.e. Navigating event was not cancelled #pragma warning disable 6506 // Both source and navigationState can accept null as valid input. internal bool Navigate(Uri source, Object navigationState, bool sandboxExternalContent, bool navigateOnSourceChanged) { if (IsDisposed) { return false; } NavigateInfo navInfo = navigationState as NavigateInfo; if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info)) { EventTrace.EventProvider.TraceEvent( EventTrace.Event.Wpf_NavigationStart, EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, navInfo != null ? navInfo.NavigationMode.ToString() : NavigationMode.New.ToString(), source != null ? "\"" + source.ToString() + "\"" : "(null)"); } Invariant.Assert(IsConsistent(navInfo)); WebRequest newRequest = null; bool isFragment = false; Uri resolvedSource = null; if (source != null) { // If it's fragment, we will need to resolve with _currentSource, // because BaseUri doesn't contain the last part of the path: filename, // and fragment navigation's context should be currentSource. // If previous navigation is object navigation, the _currentsource is null, the fragment // navigation uri can be pack://application,,,/#fragment, so check GetUriRelativeToPackAppBase(source). if (BindUriHelper.StartWithFragment(source) || BindUriHelper.StartWithFragment(BindUriHelper.GetUriRelativeToPackAppBase(source))) { resolvedSource = BindUriHelper.GetResolvedUri(_currentSource, source); isFragment = true; } else { resolvedSource = BindUriHelper.GetResolvedUri(source); // Special case (bugs 1187603 & 1187613): Navigating back/fwd to a different instance // of the current page. Then it's not fragment navigation. The test below // distinguishes back/fwd navigations within the current page from navigations // between two different instances of the same page (URI). isFragment = (navInfo == null || navInfo.JournalEntry == null || navInfo.JournalEntry.ContentId == _contentId) && IsSameUri(null, resolvedSource, _currentSource, false /* without Fragment */); } // If this is a refresh, we want to refresh the whole page so set isFragment to false // so we renavigate the whole page. if ((navInfo != null && navInfo.NavigationMode == NavigationMode.Refresh)) { isFragment = false; } // If it's Uri navigation, we allow user to configure the webrequest in Navigating event. // So we create the WebRequest here and pass it in event args. // If source != null or it's not fragment navigation, we need to create a webrequest if (!isFragment) { newRequest = CreateWebRequest(resolvedSource, navInfo); // // Check for unable to create a WebRequest. // May have delegated back to browser for x-domain case. // (`http:// only, not file--see CreateWebRequest()). // if (newRequest == null) return false; } } // HandleNavigating will call DoStopLoading which aborts current webrequest if there is any. if (HandleNavigating(resolvedSource, null, navigationState, newRequest, navigateOnSourceChanged) == false) { return false; } // Short-circuit re-navigating to null. This should be done after HandleNavigating because // there might be a pending navigation to cancel first. if (source == null && _bp == null) { ResetPendingNavigationState(NavigationStatus.Idle); return true; } // If we're navigating within the same file, try to just scroll or page // the right element into view if (isFragment) { NavigateToFragmentOrCustomContentState(resolvedSource, navigationState); return true; } if (ShouldDelegateXamlViewerNavigationToBrowser(navInfo, resolvedSource)) { try { DelegateToBrowser(newRequest is PackWebRequest, resolvedSource); } finally { ResetPendingNavigationState(NavigationStatus.Idle); } } else { // Post the navigate Dispatcher operation _navigateQueueItem.PostNavigation(); } return true; } private void InformBrowserAboutStoppedNavigation() { if (Application != null && Application.CheckAccess()) { Application.PerformNavigationStateChangeTasks(true, false, Application.NavigationStateChange.Stopped); } } #pragma warning restore 6506 // // bool Navigate(Object root, Object navigationState) // ////// Navigate to content tree. Async state can be passed across the navigation /// and can be retrieved from the Navigation events. /// ///#pragma warning disable 6506 // Both root and navigationState can accept null as vaild input. public bool Navigate(Object root, Object navigationState) { if (IsDisposed) { return false; } NavigateInfo navigateInfo = navigationState as NavigateInfo; if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info)) { EventTrace.EventProvider.TraceEvent( EventTrace.Event.Wpf_NavigationStart, EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, navigateInfo != null ? navigateInfo.NavigationMode.ToString() : NavigationMode.New.ToString(), root != null ? root.ToString() : "(null)"); } Invariant.Assert(IsConsistent(navigateInfo)); // Prevent re-starting the same PageFunction object before it has returned first. if (navigateInfo == null) // not called internally, from NavigateToParentPage() { PageFunctionBase pf = root as PageFunctionBase; // This won't detect the case when no Return event handler was attached, but then // we don't run the risk of overwriting the ReturnEventSaver. if (pf != null && (pf._Resume || pf._Saver != null)) throw new InvalidOperationException(SR.Get(SRID.InvalidOperation_CannotReenterPageFunction)); } Uri source = navigateInfo == null ? null : navigateInfo.Source; // HandleNavigating will set the pending Uri from navigationState if available // See comments in NavigateInfo class if (HandleNavigating(source, root, navigationState, null, false) == false) { return false; } // root==_bp occurs in these cases: // - Navigate(object) was called with the current Content object. This is handled as fragment // navigation, scrolling content to top. // - Refresh(). We'll go through the entire navigation sequence. // - Going back/fwd to a journal entry associated with the same object. This is also handled // as fragment navigation. if (object.ReferenceEquals(root, _bp) && (navigateInfo == null || navigateInfo.NavigationMode != NavigationMode.Refresh)) { NavigateToFragmentOrCustomContentState(source, navigationState); // Special case: Non-consecutive navigations to the same content object will create // different journal entry groups (with different ContentIds). On journal navigation, // the right state has to be restored. This is done after updating the journal so that // the journal entry created for the previous position in the journal is associated // with the right JournalEntryGroupState. if (IsJournalNavigation(navigateInfo)) { _journalEntryGroupState = navigateInfo.JournalEntry.JEGroupState; _contentId = _journalEntryGroupState.ContentId; // The JournalEntryStacks need to be invalidated after changing _contentId. (Bug 1613984) _journalScope.Journal.UpdateView(); } return true; } // Post the navigate Dispatcher operation _navigateQueueItem.PostNavigation(); return true; } #pragma warning restore 6506 // // bool INavigator.CanGoForward // /// /// Property to determine if current NavigationWindow's CanGoForward is enabled /// ///public bool CanGoForward { get { return JournalScope != null && JournalScope.CanGoForward; } } // // bool INavigator.CanGoBack // /// /// Property to determine if current NavigationWindow's CanGoBack is enabled /// ///public bool CanGoBack { get { return JournalScope != null && JournalScope.CanGoBack; } } // // bool INavigator.GoForward() // /// /// Navigate to the next entry in the Journal /// ///public void GoForward() { if (JournalScope == null) throw new InvalidOperationException(SR.Get(SRID.NoForwardEntry)); JournalScope.GoForward(); } // // bool INavigator.GoBack // /// /// Navigate to the next entry in the Journal /// ///public void GoBack() { if (JournalScope == null) throw new InvalidOperationException(SR.Get(SRID.NoBackEntry)); JournalScope.GoBack(); } // // void INavigator.StopLoading() // /// /// StopLoading aborts asynchronous navigations that haven't been processed yet or that are /// still being downloaded. SopLoading does not abort parsing of the downloaded streams. /// The NavigationStopped event is fired only if the navigation was aborted. /// ///public void StopLoading() { // Not checking for IsDisposed since that checks for app shutdown too and we need to // stop current loads during app shutdown DoStopLoading(true/*clearRecursiveNavigations*/, true/*fireEvents*/); } /// /// Stop navigations that are in progress in current and child containers. /// DoStopLoading is called from HandleNavigating and the public StopLoading. /// When called from the former it will be to stop a previous navigation in progress /// but not the source for which Navigating event is being fired.If StopLoading was called from /// any of the events raised from the Navigating call, then we will abort loading even /// the one Navigating event is being fired for /// private void DoStopLoading(bool clearRecursiveNavigations, bool fireEvents) { // Note that this will fire the event top down. // Stop binds and fire the NavigationStopped event only if there was a pending navigation bool fireStopped = false; object extraData = null; // // This method is called when the navigation is stopped/cancelled, or // when a new navigation is started. // // If the new navigation is started in the Navigated event handler for previous // navigation, in some case, it might suppress the LoadCompleted event. Then // the WebResponse object for the previous navigation might not be cleaned up. // // So moving below cleanup code here to make sure it always clean up the // webresponse object no matter if _navigateQueueItem is set or not. // // Stop parsing first. It might be async parsing. if (_asyncObjectConverter != null) { _asyncObjectConverter.CancelAsync(); _asyncObjectConverter = null; // _webResponse cannot be null for async parsing. Invariant.Assert(_webResponse != null); _webResponse.Close(); _webResponse = null; } // If _asyncObjectConverter is null, it means we called XamlReader.LoadBaml, // which we cannot stop. It is [....] operation. We get here when StopLoading is called or a // new navigation is started in NavigationProgress event handler. Parser still holds on to the stream. // We will have to wait for parsing to finish. In GetObjectFromResponse when the baml loading // call returns we will check whether the navigation has been cancelled. If it has, we will // do the cleaning up. So only close the _webResponse when we are navigated. else if ((_navStatus != NavigationStatus.Navigating) && (_webResponse != null)) { _webResponse.Close(); _webResponse = null; } // Change the state whether we have pending navigations or not because // the child NavigationServices will stop their navigations when trying to add // themselves as children and see that the parent navigation has been stopped. _navStatus = NavigationStatus.Stopped; if (_navigateQueueItem != null) { _navigateQueueItem.Stop(); if (JournalScope != null) { // When a navigation is started, this method is called, with // clearRecursiveNavigations=false. In such a case the Journal shouldn't be // reset. If it is and GoBack or GoForward occurs while the new navigation is // underway, the wrong journal entry will be selected. (Previously, GoFwd // worked when issued twice without waiting, but not when issued more than twice, // because the journal was reset.) if (clearRecursiveNavigations) { JournalScope.AbortJournalNavigation(); } } // _request can be null for object navigation if (_request != null) { // Abort the WebRequest try { // WebRequest.Abort() wants to call the AsyncCallback that was passed to // BeginGetResponse(). Our HandleWebResponse() has to know that the request was // aborted. That's why _request is cleared before calling Abort(). WOSB 1418373. WebRequest request = _request; _request = null; request.Abort(); } //These catch stmts are by design. We don't know what WebRequest object //we will end up with and which support Abort and which don't. These are //not fatal errors so we safely ignore them. #pragma warning disable 6502 //Documented exception thrown by this method catch (NotSupportedException) { } //This is what we really see, so catching both catch (NotImplementedException) { } #pragma warning restore 6502 } extraData = _navigateQueueItem.NavState; ResetPendingNavigationState(NavigationStatus.Stopped); fireStopped = true; } if (clearRecursiveNavigations && _recursiveNavigateList.Count > 0) { _recursiveNavigateList.Clear(); fireStopped = true; } if (_navigatorHostImpl != null) { _navigatorHostImpl.OnSourceUpdatedFromNavService(true /* journalOrCancel */); } // Event handler exception continuality: if exception occurs in NavigationStopped event handler, // we want to finish stopping navigation. bool succeed = false; try { if (fireEvents && fireStopped) { FireNavigationStopped(extraData); } succeed = true; } finally { // Event handler exception continuality: when trying to stop child navigation, if exception occurs for one child // we want to continue to stop the rest child navigations. int i = 0; try { // Stop all binds in the children NavigationServices // Not using the PendingNavigationList here because the child containers will add themselves // to the parent's list only if the navigation was started at the parent level. // But Stop invoked on the parent level should stop all navigations in the child tree as well // whether or not the parent itself is navigating for (; i < _childNavigationServices.Count; ++i) { // if there is an exception (succeed == false), we want to stop children's loading without // firing the events. ((NavigationService)_childNavigationServices[i]).DoStopLoading(true, succeed/*fireEvent: we only fire when succeed*/); } } finally { // If i+1 is less then the total count, it means that exception occurs in the number i child StopLoading, // we should finish stoploading for the rest of children without firing any events. if (++i < _childNavigationServices.Count) { for (; i < _childNavigationServices.Count; ++i) { ((NavigationService)_childNavigationServices[i]).DoStopLoading(true, false/*fireEvents*/); } } // We don't need to recursively fire on all XC's in the PendingNavigationList // If they added themselved to the list here, then they must be hooked up, so // the recursive call above to stop binds in the children XCs should take care of it. // The assert is to find any scenarios I missed. Debug.Assert(PendingNavigationList.Count == 0, "Navigations in child containers have not been stopped"); // Incase the Loader did not notify about bind errors (eg. exceptions that // were not caught by Loader when aborting the binds) then the List will never // be cleared. So clean it up here to be on the safe side // The assert above is for catching these conditions during development so we can fix them PendingNavigationList.Clear(); if (_parentNavigationService != null) { if (_parentNavigationService.PendingNavigationList.Contains(this)) { _parentNavigationService.PendingNavigationList.Remove(this); if (fireEvents) { // Fire LoadCompleted on the parent if appropriate. // This will happen if the navigation was started at the parent level // and navigation in this frame was stopped. _parentNavigationService.HandleLoadCompleted(null); } } } } } } // // void INavigator.Refresh() // ////// Refresh the current content /// ///public void Refresh() { if (IsDisposed) { return; } //OK to use _currentCleanSource, the Navigate codepath will take care of //handing out relative uri to events // Any pending navigations are first stopped before the page is refreshed if (CanReloadFromUri) { Navigate(_currentSource, new NavigateInfo(_currentSource, NavigationMode.Refresh)); } else if (_bp != null) { // Content refreshes are usually a no-op. We will go through the motions of the navigation // and fire the appropriate events so developers can take appropriate action eg, clearing // user input etc. This will also stop any pending navigations Navigate(_bp, new NavigateInfo(_currentSource, NavigationMode.Refresh)); } } /// /// This event is fired when an error is encountered during a navigation /// public event NavigationFailedEventHandler NavigationFailed; // // INavigator.Navigating // ////// event NavigatingCancelEventHandler NavigationService.Navigating /// ///public event NavigatingCancelEventHandler Navigating { add { _navigating += value; } remove { _navigating -= value; } } NavigatingCancelEventHandler _navigating; /// /// Fires the Navigating event and returns a bool to indicate whether a navigation is /// allowed or not /// /// /// /// /// ///bool indicating whether the Navigating is allowed or not private bool FireNavigating(Uri source, Object bp, Object navState, WebRequest request) { NavigateInfo navigateInfo = navState as NavigateInfo; Uri cleanSource = BindUriHelper.GetUriRelativeToPackAppBase(source); // For Application's startup Uri case, we navigate in NavigationService created in // ether then if there was no window tag, we create a new NavigationWindow and navigate in it // with the Content that was already created from the StartupUri navigation. This will cause // the Navigating event to fire a second time. So don't fire it a second time here // This or avoid firing the event if this container is App Startup container // and let the window fire the events instead. This means the first Navigating event will // be a little delayed and the user won't have a chance to cancel the Navigating event // until we already downloaded. if (bp != null && navigateInfo != null && !(navigateInfo is PageFunctionReturnInfo || bp is PageFunctionBase && (bp as PageFunctionBase)._Resume) && navigateInfo.Source != null && navigateInfo.NavigationMode == NavigationMode.New) { // This should happen only for the Application case when processing the Startup Uri Debug.Assert(this.Application != null && this.Application.CheckAccess() == true && IsSameUri(null, Application.StartupUri, navigateInfo.Source, false /* withFragment */), "Encountered unexpected condition in FireNavigating, see comments in the file"); // Only allow this navigation to continue if the user has not // reqeusted another navigation in the mean time. return _navigateQueueItem == null; } CustomContentState customContentState = (navigateInfo != null && navigateInfo.JournalEntry != null) ? navigateInfo.JournalEntry.CustomContentState : null; // do not expose navState if it is NavigateInfo object extraData = navigateInfo == null ? navState : null; NavigatingCancelEventArgs e = new NavigatingCancelEventArgs( cleanSource, bp, customContentState, extraData, navigateInfo == null ? NavigationMode.New : navigateInfo.NavigationMode, request, INavigatorHost, IsNavigationInitiator); if (_navigating != null) { _navigating(INavigatorHost, e); } if (!e.Cancel && this.Application != null && this.Application.CheckAccess()) { this.Application.FireNavigating(e, _bp == null); } // If this is null, the IProvideCustomContentState callback will be used later on. _customContentStateToSave = e.ContentStateToSave; if (e.Cancel) { if (JournalScope != null) { JournalScope.AbortJournalNavigation(); } } return (!e.Cancel && !IsDisposed); } // returns whether or not to navigate private bool HandleNavigating(Uri source, Object content, Object navState, WebRequest newRequest, bool navigateOnSourceChanged) { NavigateInfo navigateInfo = navState as NavigateInfo; if (navigateInfo != null) { Debug.Assert(navigateInfo.IsConsistent); Debug.Assert(source == null || navigateInfo.Source == null || IsSameUri(null, navigateInfo.Source, source, false /* withFragment */), "Source argument does not match NavigateInfo.Source"); // Don't want to overwrite one passed in if (source == null) { source = navigateInfo.Source; } } NavigateQueueItem localNavigateQueueItem = new NavigateQueueItem(source, content, navigateInfo != null ? navigateInfo.NavigationMode : NavigationMode.New, navState, this); // Set the pending state. _navigateQueue item may get overwritten in a recursive StopLoading // or Navigate call (called from FireNavigating). If so then we need to cancel this navigation // since the last StopLoading and Navigate call will supercede this call. We need to cancel // this navigation is such a case even if this event was not explicitly cancelled _recursiveNavigateList.Add(localNavigateQueueItem); // For each new navigation we need to re-determine if we are the initial navigator _isNavInitiatorValid = false; // If this is not a navigation started by Source DP change, we notify the INavigatorHost // that source changed. if ((_navigatorHostImpl != null) && (!navigateOnSourceChanged)) { _navigatorHostImpl.OnSourceUpdatedFromNavService(IsJournalNavigation(navigateInfo) /* journalOrCancel */); } // Event handler exception continuality: if exception occurs in Navigating event handler, the cleanup action is // the same as the event being cancelled. bool allowNavigation = false; try { allowNavigation = FireNavigating(source, content, navState, newRequest); } catch { CleanupAfterNavigationCancelled(localNavigateQueueItem); throw; } if (allowNavigation == true) { DoStopLoading(false /*clearRecursiveLoads*/, true /*fireEvents*/); Debug.Assert(PendingNavigationList.Count == 0, "Pending child navigations were not stopped before starting a new navigation"); // NavigationStopped event handler could have caused a new navigation. if (_recursiveNavigateList.Contains(localNavigateQueueItem) == false) return false; _recursiveNavigateList.Clear(); // Continue with the navigation Debug.Assert(_navigateQueueItem == null, "Previous nav queue item should be cleared by now."); _navigateQueueItem = localNavigateQueueItem; _request = newRequest; _navStatus = NavigationStatus.Navigating; } else { CleanupAfterNavigationCancelled(localNavigateQueueItem); } return allowNavigation; } private void CleanupAfterNavigationCancelled(NavigateQueueItem localNavigateQueueItem) { if (JournalScope != null) { JournalScope.AbortJournalNavigation(); } // If event was canceled then we need to remove it. // If the event was canceled AND superceded by StopLoading or Navigate, it won't be // in the list but Remove won't throw an exception so not doing an if check here // Don't clear the whole list here since this could be an intermediate Navigate in a recursive callstack // and the caller could now proceed with the navigation _recursiveNavigateList.Remove(localNavigateQueueItem); if (_navigatorHostImpl != null) { _navigatorHostImpl.OnSourceUpdatedFromNavService(true /* journalOrCancel */); } // TFS Dev10 451993 - Browser downloading state not reset; case 4. InformBrowserAboutStoppedNavigation(); } // // INavigator.Navigated // ////// event NavigatedEventHandler NavigationService.Navigated /// ///public event NavigatedEventHandler Navigated { add { _navigated += value; } remove { _navigated -= value; } } NavigatedEventHandler _navigated; private void FireNavigated(object navState) { // do not expose navState if it is NavigateInfo object extraData = navState is NavigateInfo ? null : navState; // Event handler exception continuality: if exception occurs in Navigated event handler, the cleanup action is // the same as StopLoading(). try { // NavigationEventArgs e = new NavigationEventArgs(CurrentSource, Content, extraData, _webResponse, INavigatorHost, IsNavigationInitiator); if (_navigated != null) { _navigated(INavigatorHost, e); } // Fire it on the Application if (this.Application != null && this.Application.CheckAccess()) { this.Application.FireNavigated(e); } } catch { DoStopLoading(true, false); throw; } } /// /// Critical because it sets BrowserInterop.IsInitialViewerNavigation /// Safe because IsInitialViewerNavigation is set to false and this method completes a navigation. /// Any further navigation will not be considered "initial". /// /// Consider: Do we really need IsInitialViewerNavigation to be Critical? No security decision is done /// based on that. /// [SecurityCritical, SecurityTreatAsSafe] private void HandleNavigated(object navState, bool navigatedToNewContent) { Debug.Assert(_navStatus == NavigationStatus.Navigated); UpdateAddressBarForLooseXaml(); BrowserInteropHelper.IsInitialViewerNavigation = false; NavigateInfo navInfo = navState as NavigateInfo; // For scrolling to #fragment and for restoring root viewer state, the FC/FCE.Loaded event // is preferably used. (It occurs before first rendering.) If _bp is neither FE nor FCE, // we fall back to ContentRendered (wired in the INavigatorHost setter). bool handleContentLoadedEvent = false; if (navigatedToNewContent && _currentSource != null) { // Scrolling to named target element may not succeed before first layout is done. string fragment = BindUriHelper.GetFragment(_currentSource); handleContentLoadedEvent = !string.IsNullOrEmpty(fragment); } if (navInfo != null && navInfo.JournalEntry != null) // Was this journal navigation? { JournalEntry je = navInfo.JournalEntry; if (je.CustomContentState != null) { je.CustomContentState.Replay(this, navInfo.NavigationMode); je.CustomContentState = null; // Object not needed anymore. if (_navStatus != NavigationStatus.Navigated) return; // Replay() probably started another navigation. } // Note: navInfo.Restore(), which restores the controls state, is called earlier in // the navigation sequence, from HookupNewTree(). This should be done only on // Content (_bp) change, whereas CustomContentState is restored after each // custom journal entry navigation or fragment navigation. if (je.RootViewerState != null && _navigatorHostImpl != null) { if (!navigatedToNewContent) { RestoreRootViewerState(je.RootViewerState); je.RootViewerState = null; } else { // Template may not be applied yet. Need to wait for layout. // (Even if there is currently a Visual under the navigatorHost's ContentPresenter, // it may be associated with the previous Content object.) handleContentLoadedEvent = true; } } } if (handleContentLoadedEvent) { FrameworkContentElement fce = _bp as FrameworkContentElement; if (fce != null) { fce.Loaded += OnContentLoaded; } else { FrameworkElement fe = _bp as FrameworkElement; if (fe != null) { fe.Loaded += OnContentLoaded; } } // ContentRendered handling will be canceled in the Loaded handler. _cancelContentRenderedHandling = false; } if (JournalScope != null) { NavigateQueueItem currentItem = _navigateQueueItem; // The view may need to be changed if NavigationContainers came or went JournalScope.Journal.UpdateView(); // Immediately stop processing this navigation - its been preempted // by another navigation from the browser if (_navigateQueueItem != currentItem) { return; } } ResetPendingNavigationState(NavigationStatus.Navigated); FireNavigated(navState); // PF.Start is called after Navigated per spec if (navigatedToNewContent && IsPageFunction(_bp)) { HandlePageFunction(navInfo); } HandleLoadCompleted(navState); } // // INavigator.NavigationProgress // ////// event NavigationProgressEventHandler NavigationService.NavigationProgress /// ///public event NavigationProgressEventHandler NavigationProgress { add { _navigationProgress += value; } remove { _navigationProgress -= value; } } NavigationProgressEventHandler _navigationProgress; private void FireNavigationProgress(Uri source) { // Fire accessibility event for Frame, NavigationWindow, etc. UIElement navigatorHost = INavigatorHost as UIElement; if (navigatorHost != null) { AutomationPeer peer = UIElementAutomationPeer.FromElement(navigatorHost) as AutomationPeer; if (peer != null) { NavigationWindowAutomationPeer.RaiseAsyncContentLoadedEvent(peer, BytesRead, MaxBytes); } } NavigationProgressEventArgs e = new NavigationProgressEventArgs(source, BytesRead, MaxBytes, INavigatorHost); // Event handler exception continuality: if exception occurs in NavigationProgress event handler, the cleanup action is // the same as StopLoading(). try { if (_navigationProgress != null) { _navigationProgress(INavigatorHost, e); } if (this.Application != null && this.Application.CheckAccess()) { this.Application.FireNavigationProgress(e); } } catch { DoStopLoading(true, false); throw; } } // // INavigator.LoadCompleted // /// /// event LoadCompletedEventHandler NavigationService.LoadCompleted /// ///public event LoadCompletedEventHandler LoadCompleted { add { _loadCompleted += value; } remove { _loadCompleted -= value; } } LoadCompletedEventHandler _loadCompleted; private void FireLoadCompleted(bool isNavInitiator, object navState) { EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.Wpf_NavigationEnd); // do not expose navState if it is NavigateInfo object extraData = navState is NavigateInfo ? null : navState; NavigationEventArgs e = new NavigationEventArgs(CurrentSource, Content, extraData, _webResponse, INavigatorHost, isNavInitiator); // Event handler exception continuality: if exception occurs in LoadCompleted event handler, the cleanup action is // the same as StopLoading(). try { if (_loadCompleted != null) { // If the Navigator is Frame or NavigationWindow, the // relative event handlers would be called here. // Since Frame and NavigationWIndow just transferred their // event handlers to their own NavigationService. _loadCompleted(INavigatorHost, e); } if (this.Application != null && this.Application.CheckAccess()) { this.Application.FireLoadCompleted(e); } } catch { DoStopLoading(true, false); throw; } } #region FragmentNavigation Event /// /// This event is fired when the navigating uri contains a fragment. /// It allows the listeners to take a custom action when a fragment is /// encountered. /// public event FragmentNavigationEventHandler FragmentNavigation { add { _fragmentNavigation += value; } remove { _fragmentNavigation -= value; } } private FragmentNavigationEventHandler _fragmentNavigation; // Returns true if a listener has handled the fragment and no more processing is necessary // False indicates that NavigationService should continue with the default behaviour private bool FireFragmentNavigation(string fragment) { if (string.IsNullOrEmpty(fragment)) { // A navigation to a null or empty fragment is a scroll to the top of the page. // This is not intuitively a fragment navigation so we should not fire this event. return false; } FragmentNavigationEventArgs e = new FragmentNavigationEventArgs(fragment, INavigatorHost); // Event handler exception continuality: if exception occurs in FragmentNavigation event handler, the cleanup action is // the same as StopLoading(). try { if (_fragmentNavigation != null) { _fragmentNavigation(this, e); } if (Application != null && Application.CheckAccess()) { Application.FireFragmentNavigation(e); } } catch { DoStopLoading(true, false); throw; } return e.Handled; } #endregion //// Fire load completed on current NavigationService first. // Remove the search entity from its ParentNavigationService's pendinglist, // if the parent NavigationService's pendinglist reaches to Zero, Fire // the loadcompleted event on the ParentNavigationService. // private void HandleLoadCompleted(object navState) { // if this is this frame finishing we need to remember navState until all children fire if (navState != null) { _navState = navState; } // If it was async parsing and _asyncObjectConverter is not null here, it means // parser is not done with parsing the stream (async parsing). This is currently the only case that this could happen. // When parser is done, OnStreamClosed will be called where _asyncObjectConverter will be set to null. if (_asyncObjectConverter != null) return; // Not the right time to fire it // need to save navState if it is non null if (!(PendingNavigationList.Count == 0 && _navStatus == NavigationStatus.Navigated)) return; NavigationService ncParent = this.ParentNavigationService; /* */ _navStatus = NavigationStatus.Idle; bool isNavInitiator = IsNavigationInitiator; FireLoadCompleted(isNavInitiator, _navState); // now that we have fired LoadComplete we do not need to remember our navigation state (extra data) or the web response _navState = null; // Response object should be closed so that the underlying connection can be // used for the subsequent requests. Waiting for GC to close the object could be too late for // some scenarios. // Do not close and null it before firing LoadCompleted because we pass webresponse out in Navigated and LoadCompleted event args. if (_webResponse != null) { _webResponse.Close(); _webResponse = null; } if (!isNavInitiator && ncParent != null) { ncParent.PendingNavigationList.Remove(this); // Inform parent so it can Fire LoadCompleted if appropriate ncParent.HandleLoadCompleted(null); } } // // INavigator.NavigationStopped // ////// event NavigationStoppedEventHandler NavigationService.NavigationStopped /// ///public event NavigationStoppedEventHandler NavigationStopped { add { _stopped += value; } remove { _stopped -= value; } } NavigationStoppedEventHandler _stopped; private void FireNavigationStopped(object navState) { // do not expose navState if it is NavigateInfo object extraData = navState is NavigateInfo ? null : navState; NavigationEventArgs e = new NavigationEventArgs(Source, Content, extraData, null, INavigatorHost, IsNavigationInitiator); if (_stopped != null) { _stopped(INavigatorHost, e); } if (this.Application != null && this.Application.CheckAccess()) { this.Application.FireNavigationStopped(e); } } // FE/FCE.Loaded is raised right after the first layout, before render. private void OnContentLoaded(object sender, RoutedEventArgs args) { Debug.Assert(sender == _bp); FrameworkContentElement fce = _bp as FrameworkContentElement; if (fce != null) { fce.Loaded -= OnContentLoaded; } else { ((FrameworkElement)_bp).Loaded -= OnContentLoaded; } OnFirstContentLayout(); _cancelContentRenderedHandling = true; } private void ContentRenderedHandler(object sender, EventArgs args) { EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.Wpf_NavigationContentRendered); if (_cancelContentRenderedHandling) { _cancelContentRenderedHandling = false; } else { OnFirstContentLayout(); } } private void OnFirstContentLayout() { // Scrolling will fail unless layout is guaranteed to be done, hence dealing with this here. if (CurrentSource != null) { // First scroll to the fragment if there was one in the URI string fragment = BindUriHelper.GetFragment(CurrentSource); if (!string.IsNullOrEmpty(fragment)) { // The main navigation has succeeded so fail silently if element with the ID // was not found or if scrolling fails. this.NavigateToFragment(fragment, false); } } // Restore root viewer state. This is in case HandleNavigated() couldn't do it. if (_journalScope != null) { JournalEntry je = _journalScope.Journal.CurrentEntry; if (je != null && je.RootViewerState != null) { RestoreRootViewerState(je.RootViewerState); je.RootViewerState = null; } } } #endregion INavigator Implementation # endregion public method and property internal void DoNavigate(Uri source, NavigationMode f, Object navState) { /* */ EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.Wpf_NavigationAsyncWorkItem); // Because shutdown is completed asynchronously, the DoNavigate callback might be called // in the meantime. if (IsDisposed) return; // Get or BeginGet WebResponse // Special handling PackWebRequest, because it only support [....] right now. // D2's plan is to support async in V2. Refer to PS #18958 and #17386. // We should switch to async after those tasks are done. WebResponse response = null; try { if (_request is PackWebRequest) { response = WpfWebRequestHelper.GetResponse(_request); if (response == null) { Uri requestUri = BindUriHelper.GetUriRelativeToPackAppBase(_request.RequestUri); throw new Exception(SR.Get(SRID.GetResponseFailed, requestUri.ToString())); } // Have to use source instead of _request.RequestUri because the work around we put in // to make fragment work with FileWebRequest. See function CreateWebRequest for details. // Get Object from response GetObjectFromResponse(_request, response, source, navState); } else { // Have to use source instead of _request.RequestUri because the work around we put in // to make fragment work with FileWebRequest. See function CreateWebRequest for details. RequestState requestState = new RequestState(_request, source, navState, Dispatcher.CurrentDispatcher); // Async WebResponse for everything other than PackWebRequest _request.BeginGetResponse(new AsyncCallback(HandleWebResponseOnRightDispatcher), requestState); } } // Catch WebException and IOException specifically so other types of exceptions do not lose the context. catch (WebException e) { object extraData = navState is NavigateInfo ? null : navState; if (! FireNavigationFailed(new NavigationFailedEventArgs(source, extraData, INavigatorHost, _request, response, e))) { throw; } } catch (IOException e) { object extraData = navState is NavigateInfo ? null : navState; if (! FireNavigationFailed(new NavigationFailedEventArgs(source, extraData, INavigatorHost, _request, response, e))) { throw; } } } private bool FireNavigationFailed(NavigationFailedEventArgs e) { _navStatus = NavigationStatus.NavigationFailed; // Event handler exception continuality: if exception occurs in NavigationFailed event handler, the cleanup action is // the same as StopLoading(). try { if (NavigationFailed != null) { NavigationFailed(INavigatorHost, e); } if (!e.Handled) { NavigationWindow navWin = FindNavigationWindow(); if ((navWin != null) && (navWin.NavigationService != this)) { navWin.NavigationService.FireNavigationFailed(e); } } if (!e.Handled && this.Application != null && this.Application.CheckAccess()) { this.Application.FireNavigationFailed(e); } } finally { if (_navStatus == NavigationStatus.NavigationFailed) { DoStopLoading(true, false); } } return e.Handled; } // // Create a web-request. // May delegate to the browser for cross-domain case. // Will return null if unable to create a web-request. // private WebRequest CreateWebRequest(Uri resolvedDestinationUri, NavigateInfo navInfo) { WebRequest request = null; // Ideally we would want to use RegisterPrefix and WebRequest.Create. // However, these two functions regress 700k working set in System.dll and System.xml.dll // which is mostly for logging and config. // Call PackWebRequestFactory.CreateWebRequest to bypass the regression if possible // by calling Create on PackWebRequest if uri is pack scheme try { request = PackWebRequestFactory.CreateWebRequest(resolvedDestinationUri); } catch (NotSupportedException) { LaunchResult launched = LaunchResult.NotLaunched; // Not supported exceptions are thrown for mailto: which we want to support. // So we detect mailto: here. launched = AppSecurityManager.SafeLaunchBrowserOnlyIfPossible(CurrentSource, resolvedDestinationUri, IsTopLevelContainer); if (launched == LaunchResult.NotLaunched) throw; } catch (SecurityException e) { LaunchResult launched = LaunchResult.NotLaunched; // the scenario this code is enabling is navigation to Uri's outside of the app // for top-level. // So for example at an express app at domain http://www.example.com // click on a hyperlink to http://www.msn.com // We will get a security exception on the attempt to access msn. // So we delegate back to the top-level browser. // // IMPORTANT: Creating a WebRequest for a file:// URI doesn't fail here if the URI is outside // the site of origin. Instead, WebRequest.EndGetResponse() will throw SecurityException. // There is a similar case for such URIs there. // Callers of this method should not assume that the application has access to the given URI. if (e.PermissionType == typeof(System.Net.WebPermission)) { launched = AppSecurityManager.SafeLaunchBrowserOnlyIfPossible(CurrentSource, resolvedDestinationUri, IsTopLevelContainer); } if (launched == LaunchResult.NotLaunched) throw; } bool isRefresh = navInfo == null ? false : navInfo.NavigationMode == NavigationMode.Refresh; WpfWebRequestHelper.ConfigCachePolicy(request, isRefresh); return request; } // Async WebResponse callback. // This can be called on any thread. Find the right dispatcher and call on that private void HandleWebResponseOnRightDispatcher(IAsyncResult ar) { if (IsDisposed) { return; } Dispatcher callbackDispatcher = ((RequestState)ar.AsyncState).CallbackDispatcher; if (Dispatcher.CurrentDispatcher != callbackDispatcher) { callbackDispatcher.BeginInvoke( DispatcherPriority.Normal, (DispatcherOperationCallback)delegate(object unused) { HandleWebResponse(ar); return null; }, null); } else { // // Since this is for Async WebResponse call, this method call // is out of the DispatcherOperation handling, and then out of // the Dispatcher.WrappedInvoke scope. // If an exception is raised inside HanldeWebRespone, the Dispatcher // UnhandledException handler should have chance to catch it. // callbackDispatcher.Invoke( DispatcherPriority.Send, (DispatcherOperationCallback)delegate(object unused) { HandleWebResponse(ar); return null; }, null); } } private void HandleWebResponse(IAsyncResult ar) { if (IsDisposed) { return; } EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.Wpf_NavigationWebResponseReceived); // we neeed source, navState (get from ar.AsyncState) RequestState requestState = (RequestState)ar.AsyncState; // We don't keep a list of previous WebRequests that has been made, because // at any time we only handle one WebRequest. If a new WebRequest comes in // before the previous one finishes, the previous one is aborted. // However if a WebRequest.Abort() is called before its aync callback is called, // the async callback will still be called. So we need to check the request. Don't // do anything if it's not the current request. if (requestState.Request != _request) { return; } WebResponse response = null; try { try { response = WpfWebRequestHelper.EndGetResponse(_request, ar); } catch (WebException we) { // this codepath enables top-level navigation to UNC content. Unlike HttpWebRequest, FileWebRequest throws on EndGetResponse() LaunchResult launched = LaunchResult.NotLaunched; SecurityException se = we.GetBaseException() as SecurityException; // delegate to the browser only if 1) navigating to UNC and 2) reason for which we couldn't get the stream is no grants if (_request.RequestUri.IsUnc && _request.RequestUri.IsFile && se != null && se.PermissionType == typeof(FileIOPermission)) { launched = AppSecurityManager.SafeLaunchBrowserOnlyIfPossible(CurrentSource, _request.RequestUri, IsTopLevelContainer); } if (launched == LaunchResult.NotLaunched) throw; // we successfully delegated navigation to the browser; return return; } // response object will be closed at approrpiate time when it is not used anymore later. GetObjectFromResponse(_request, response, requestState.Source, requestState.NavState); } // Catch WebException and IOException specifically so other types of exceptions do not lose the context. catch (WebException e) { object extraData = requestState.NavState is NavigateInfo ? null : requestState.NavState; if (! FireNavigationFailed(new NavigationFailedEventArgs(requestState.Source, extraData, INavigatorHost, _request, response, e))) { throw; } } catch (IOException e) { object extraData = requestState.NavState is NavigateInfo ? null : requestState.NavState; if (! FireNavigationFailed(new NavigationFailedEventArgs(requestState.Source, extraData, INavigatorHost, _request, response, e))) { throw; } } } private bool CanUseTopLevelBrowserForHTMLRendering() { return (IsTopLevelContainer && IsParentedByBrowserWindow() ); } // Create Object from the return of WebResponse stream private void GetObjectFromResponse(WebRequest request, WebResponse response, Uri destinationUri, Object navState) { bool fHoldResponse = false; ContentType contentType = WpfWebRequestHelper.GetContentType(response); try { Stream s = response.GetResponseStream(); if (s == null) { Uri requestUri = BindUriHelper.GetUriRelativeToPackAppBase(_request.RequestUri); throw new Exception(SR.Get(SRID.GetStreamFailed, requestUri.ToString())); } long contentLength = response.ContentLength; Uri cleanSource = BindUriHelper.GetUriRelativeToPackAppBase(destinationUri); NavigateInfo navigateInfo = navState as NavigateInfo; bool sandBoxContent = SandboxExternalContent && (! BaseUriHelper.IsPackApplicationUri(destinationUri)) && MimeTypeMapper.XamlMime.AreTypeAndSubTypeEqual(contentType); // this code path is disabled in partial trust because it currently violates P3P if (sandBoxContent == true && !SecurityHelper.CheckUnmanagedCodePermission()) { sandBoxContent = false; } // BindStream overrides Read() and calls icc.OnNavigationProgress every 1k byte read BindStream bindStream = new BindStream(s, contentLength, cleanSource, (IContentContainer)this, Dispatcher.CurrentDispatcher); Invariant.Assert((_webResponse == null) && (_asyncObjectConverter == null)); _webResponse = response; _asyncObjectConverter = null; Invariant.Assert(!ShouldDelegateXamlViewerNavigationToBrowser(navigateInfo, destinationUri), "TopLevel navigation away from loose xaml is already delageted to browser. It should never reach here."); // CanUseTopLevelBrowserForHTMLRendering() will be true for TopLevel navigation away from browser hosted app. If that is the case // o will be null. Object o = MimeObjectFactory.GetObjectAndCloseStream(bindStream, contentType, destinationUri, CanUseTopLevelBrowserForHTMLRendering(), sandBoxContent, true /*allowAsync*/, IsJournalNavigation(navigateInfo), out _asyncObjectConverter); if (o != null) { // We don't keep a list of previous WebRequests that has been made, because // at any time we only handle one WebRequest. If a new WebRequest comes in // before the previous one finishes, the previous one is aborted. // However, today we cannot abort LoadXaml and LoadBaml, if user starts a new navigation in Initilaized // event handler, the currrent navigation has been cancelled, we should not call OnContentReady // when the request we start with is the same as the current one. if (_request == request) { ((IContentContainer)this).OnContentReady(contentType, o, destinationUri, navState); fHoldResponse = true; } } else { try { // If o == null, it means we don't know how to convert it. // Currently that's everything other than xaml, baml and html at site // of origin. If this is not a TopLevelContainer, we will throw an exception // if there is no converter for it, else we will try to launch the // browser if safe to do so. // For loose XAML viewing, we can get in this situation if the web server doesn't // return the right MIME type. UrlMon in IE 7+ has some heuristics based on file extension // to detect XAML, so PresentationHost may get invoked, but our // WpfWebRequestHelper.GetContentType() fails to do the same inference. In particular, // it appears that UrlMon looks at the Content-Disposition HTTP header, but we don't. if (!IsTopLevelContainer || BrowserInteropHelper.IsInitialViewerNavigation) { throw new InvalidOperationException(SR.Get(SRID.FailedToConvertResource)); } DelegateToBrowser(response is PackWebResponse, destinationUri); // Beware reentrancy in the context of the outgoing DelegateNavigation call: // The browser will send us the BrowseStop command before returning from Navigate(). // This will lead to DoStopLoading(), which will abort the WebReqest. } finally { DrainResponseStreamForPartialCacheFileBug(s); s.Close(); // Should clean the state. ResetPendingNavigationState(_navStatus); } } } finally { // If the code doesn't want to hold the webresponse, close it now. // otherwise, close the response object when the Navigation is done, // or when the navigation is stopped. if (!fHoldResponse) { response.Close(); _webResponse = null; if (_asyncObjectConverter != null) { _asyncObjectConverter.CancelAsync(); _asyncObjectConverter = null; } } } } private void DelegateToBrowser(bool isPack, Uri destinationUri) { try { if (isPack) { destinationUri = BaseUriHelper.ConvertPackUriToAbsoluteExternallyVisibleUri(destinationUri); } if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordHosting, EventTrace.Level.Info)) { EventTrace.EventProvider.TraceEvent( EventTrace.Event.Wpf_NavigationLaunchBrowser, EventTrace.Keyword.KeywordHosting, EventTrace.Level.Info, destinationUri.ToString()); } AppSecurityManager.SafeLaunchBrowserDemandWhenUnsafe(CurrentSource, destinationUri, IsTopLevelContainer); } finally { // TFS Dev10 451993 - Browser downloading state not reset; cases 2 and 3. InformBrowserAboutStoppedNavigation(); } } private void DrainResponseStreamForPartialCacheFileBug(Stream s) { // Drain the stream and launch the browser // We need to drain the response stream to work around issues with // partial cache files in the wininet cache. // We request CLR to use the wininet cache for http webrequests // When we abort a download in managed code, CLR still commits the // partial file to wininet cache (Temporary Internet Files folder) // When this file is renavigated to from IE, IE does NOT try to // redownload the file if the cache entry has not expired nor will // it try to complete the previous download. // Opened tracking bug 895912 in Windows Data base. VSWhidbey bug // is linked to it // Check CachePolicy here because we plan to expose WebRequest & WebResponse // in Navigating/Navigated event and allow user to configure it. So we want // to check cache policy here. if ((_request is HttpWebRequest) && (HttpWebRequest.DefaultCachePolicy != null) && (HttpWebRequest.DefaultCachePolicy is HttpRequestCachePolicy)) { // Use reader for its ReadToEnd ability because response.ContentLength // could not be set for HttpWebRequest. It depends on Transfer-Encoding. // If Transfer_Encoding is chunked, ContentLength will not be available. StreamReader reader = new StreamReader(s); reader.ReadToEnd(); reader.Close(); } } internal void DoNavigate(Object bp, NavigationMode navFlags, Object navState) { /* */ EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordHosting | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.Wpf_NavigationAsyncWorkItem); // Because shutdown is completed asynchronously, the DoNavigate callback might be called // in the meantime. if (IsDisposed) return; NavigateInfo navigateInfo = navState as NavigateInfo; Debug.Assert(IsConsistent(navigateInfo)); Invariant.Assert(navFlags != NavigationMode.Refresh ^ object.ReferenceEquals(bp, _bp), "Navigating to the same object should be handled as fragment navigation, except for Refresh."); Uri source = navigateInfo == null ? null : navigateInfo.Source; // The baseUri passed to GetResolvedUri() is null because here we have a new Content // object. Its URI is not resolved relative to the URI of the previous Content. Uri resolvedSource = BindUriHelper.GetResolvedUri(null, source); ((IContentContainer)this).OnContentReady(null, bp, resolvedSource, navState); } /// Updates the Journal for a navigation that has completed successfully. ////// Can't journal by serializing with a URI. /// ////// Can't journal by URI without a URI. /// ////// Critical: accesses the browsercallback services (via Journal) /// TreatAsSafe: only uses bcs to update the journal with already valid content. /// ///_bp is still the previous content (before the new navigation). It can be null. /// destinationJournalEntry can be null. /// No journal entry is created for certain types of Content or when there is no /// NavigationWindow [which is where the Journal is]. /// [SecurityCritical, SecurityTreatAsSafe] private JournalEntry UpdateJournal( NavigationMode navigationMode, JournalReason journalReason, JournalEntry destinationJournalEntry) { Debug.Assert(navigationMode == NavigationMode.New || navigationMode == NavigationMode.Back || navigationMode == NavigationMode.Forward, "The journal should not be updated on Refresh."); // The point of this assert is that there should be no destinationJournalEntry for // navigationMode=New, but it is always required for Back/Fwd. Debug.Assert(destinationJournalEntry == null ^ (navigationMode == NavigationMode.Back || navigationMode == NavigationMode.Forward)); JournalEntry journalEntry = null; if (!_doNotJournalCurrentContent) { journalEntry = MakeJournalEntry(journalReason); } if (journalEntry == null) { _doNotJournalCurrentContent = false; // This case will be true when we have navigated to null and then gone back. We cannot add null to the journal // but we still need to commit the back navigation to the journal so the journal state stays sane. if ((navigationMode == NavigationMode.Back || navigationMode == NavigationMode.Forward) && JournalScope != null) { JournalScope.Journal.CommitJournalNavigation(destinationJournalEntry); } // There's no need to do anything here for a New navigation. return null; } // EnsureJournal() should be called no earlier than here. Only the second navigation in a // NavigationService really requires a journal. // In particular, a child Frame should not be forced to create its own journal when it // is being re-navigated by DataStreams.Load(), because it doesn't yet have access to the // parent JournalNavigationScope. JournalNavigationScope journalScope = EnsureJournal(); if (journalScope == null) { return null; } PageFunctionBase pfBase = _bp as PageFunctionBase; if (pfBase != null) { // PageFunctions that don't show UI don't get navigated to in the journal // We still need to add it to the journal since we need to resume this when its child finishes // This codepath is not executed if this pagefunction finished without launching a child. // That case is handled in HandleFinish if (navigationMode == NavigationMode.New && pfBase.Content == null) { journalEntry.EntryType = JournalEntryType.UiLess; } } journalScope.Journal.UpdateCurrentEntry(journalEntry); if (journalEntry.IsNavigable()) { CallUpdateTravelLog(navigationMode == NavigationMode.New); } if (navigationMode == NavigationMode.New) { journalScope.Journal.RecordNewNavigation(); } else // Back or Forward { journalScope.Journal.CommitJournalNavigation(destinationJournalEntry); } _customContentStateToSave = null; // not needed anymore return journalEntry; } ////// Makes the appropriate kind of journal entry for the current Content and its state. /// For certain types of content, no journal entry is created (null is returned). /// internal JournalEntry MakeJournalEntry(JournalReason journalReason) { if (_bp == null) { return null; } Debug.Assert(_contentId != 0 && (_journalEntryGroupState == null || _journalEntryGroupState.ContentId == _contentId)); if (_journalEntryGroupState == null) // First journal entry created for the current Content? { _journalEntryGroupState = new JournalEntryGroupState(_guidId, _contentId); } JournalEntry journalEntry; bool keepAlive = IsContentKeepAlive(); PageFunctionBase pfBase = _bp as PageFunctionBase; if (pfBase != null) { if (keepAlive) { journalEntry = new JournalEntryPageFunctionKeepAlive(_journalEntryGroupState, pfBase); } else { // // If the PageFunction is navigated from xaml Uri, or navigated from an instance of // PageFunction type, but that PageFunctin type is implemented from xaml file, // we should always get the BaseUri DP value for the root PageFunction element. // // If the code navigates to pure #fragment, the root element should be ready, // if the BaseUri for that root element is set, we should still use JournalEntryPageFunctionUri. // if the BaseUri for that root element is not set, that pagefunction class is not // implemented in xaml file, JournalEntryPageFunctionType is used for journaling. // Navigation service has its own way to get to the element marked by the pure fragment. // Uri baseUri = pfBase.GetValue(BaseUriHelper.BaseUriProperty) as Uri; if (baseUri != null) { Invariant.Assert(baseUri.IsAbsoluteUri == true, "BaseUri for root element should be absolute."); Uri markupUri; // // Set correct uri when creating instance of JournalEntryPageFunctionUri // // This markupUri is used to create instance of PageFunction from baml stream. // fragment in original Source doesn't affect the resource loading, and it will // be set in the JournalEntry.Source for further navigation handling. So the logic // of setting markupUri for JEPFUri can be simplified as below: // // If _currentCleanSource is set and it is not a pure fragment uri, take whatever // value of _currentSource, which should always be an absolute Uri for the page. // // For all other cases, take whatever value of BaseUri in root element. // if (_currentCleanSource != null && BindUriHelper.StartWithFragment(_currentCleanSource) == false ) { markupUri = _currentSource; } else { markupUri = baseUri; } journalEntry = new JournalEntryPageFunctionUri(_journalEntryGroupState, pfBase, markupUri); } else { journalEntry = new JournalEntryPageFunctionType(_journalEntryGroupState, pfBase); } } journalEntry.Source = _currentCleanSource; // This could be #fragment. } else { if (keepAlive) { journalEntry = new JournalEntryKeepAlive(_journalEntryGroupState, _currentCleanSource, _bp); } else { journalEntry = new JournalEntryUri(_journalEntryGroupState, _currentCleanSource); } } // _customContentStateToSave can be preset by AddBackEntry() or FireNavigating(). // If not, try the IProvideCustomContentState callback. CustomContentState ccs = _customContentStateToSave; if (ccs == null) { IProvideCustomContentState pccs = _bp as IProvideCustomContentState; if (pccs != null) { ccs = pccs.GetContentState(); } } if (ccs != null) { // Make sure the object is serializable Type type = ccs.GetType(); if (!type.IsSerializable) { throw new SystemException(SR.Get(SRID.CustomContentStateMustBeSerializable, type)); } journalEntry.CustomContentState = ccs; } // Info: CustomContentState for the current page in child frames is saved in // DataStreams.SaveState(). (This requires the IProvideCustomContentState to be implemented.) // Root Viewer journaling if (_rootViewerStateToSave != null) // state saved in advance? { journalEntry.RootViewerState = _rootViewerStateToSave; _rootViewerStateToSave = null; } else { journalEntry.RootViewerState = GetRootViewerState(journalReason); } // Set the friendly Name of this JournalEntry, it will be used to display // in the drop-down list on the Back/Forward buttons // Journal entries aren't recycled when going back\forward. A new JournalEntry is always created, so // we need to set the name each time // string name = null; if (journalEntry.CustomContentState != null) { name = journalEntry.CustomContentState.JournalEntryName; } if (string.IsNullOrEmpty(name)) { DependencyObject dependencyObject = _bp as DependencyObject; if (dependencyObject != null) { name = (string)dependencyObject.GetValue(JournalEntry.NameProperty); if (String.IsNullOrEmpty(name) && dependencyObject is Page) { name = (dependencyObject as Page).Title; } } if (!String.IsNullOrEmpty(name)) { if (_currentSource != null) { string fragment = BindUriHelper.GetFragment(_currentSource); if (!string.IsNullOrEmpty(fragment)) { name = name + "#" + fragment; } } } else { // Page.WindowTitle is just a shortcut to Window.Title. // The window title is used as a journal entry name only for a top-level container. NavigationWindow navWin = JournalScope == null ? null : JournalScope.NavigatorHost as NavigationWindow; if (navWin != null && this == navWin.NavigationService && !String.IsNullOrEmpty(navWin.Title)) { if (CurrentSource != null) { name = String.Format(CultureInfo.CurrentCulture, "{0} ({1})", navWin.Title, JournalEntry.GetDisplayName(_currentSource, SiteOfOriginContainer.SiteOfOrigin)); } else { name = navWin.Title; } } else { // if not title was set we use the uri if it is available. if (CurrentSource != null) { name = JournalEntry.GetDisplayName(_currentSource, SiteOfOriginContainer.SiteOfOrigin); } else { name = SR.Get(SRID.Untitled); } } } } journalEntry.Name = name; if (journalReason == JournalReason.NewContentNavigation) { journalEntry.SaveState(_bp); } return journalEntry; } ////// Called by ApplicationProxyInternal when a XAML Browser Application is about to be shut down /// and the entire journal needs to be serialized. /// A semi-bogus Navigating event is raised to give the application a chance to provide a /// CustomContentState, in case it doesn't implement IProvideCustomContentState [Mongoose]. /// (In case it does, the event is still raised for consistency.) /// internal void RequestCustomContentStateOnAppShutdown() { _isNavInitiator = false; _isNavInitiatorValid = true; // prevent updating the brower's status FireNavigating(null, null, null, null); // sets _customContentStateToSave } ////// Critical - elevates permissions to call UpdateTravellog by calling a SUC'ed PInvoke /// TreatAsSafe - we're callling to update the travellog. /// information passed back is whether we're a top-level container, and whether to add a new entry. /// /// Net effect is creation of a new journal entry. Considered safe as the information stored in the journal is derived from what's been navigated to. /// We could have the same effect via programmatically allowing a navigate OR calling NavigationWindow.GoBack(). /// [SecurityCritical, SecurityTreatAsSafe] internal void CallUpdateTravelLog(bool addNewEntry) { // Not explicitly checking IsSerializable here because we will be called back // immediately via SaveHistory which will throw the serialization exception which // will give us the same effect and without the overhead of an explicit // GetType + type.IsSerializable check. But if the subclass has data members // that are not serializable we will still throw an exception inspite of // IsSerializable == true for the subclass. if (HasTravelLogIntegration) { this.Application.BrowserCallbackServices.UpdateTravelLog(addNewEntry); } } ////// Returns the current Application /// internal Application Application { get { return Application.Current; } } internal bool AllowWindowNavigation { private get { return _allowWindowNavigation; } set { _allowWindowNavigation = value; } } internal long BytesRead { get { return _bytesRead; } set { _bytesRead = value; } } internal long MaxBytes { get { return _maxBytes; } set { _maxBytes = value; } } ///internal uint ContentId { #if DEBUG [DebuggerStepThrough] #endif get { return _contentId; } } internal Guid GuidId { get { return _guidId; } set { _guidId = value; } } /// /// NOTE that the tree of NavigationServices may comprise multiple JournalNavigationScopes. /// So, it is possible that this NS has a parent NS but is also the root NS for a JNS /// (IsJournalLevelContainer==true). (Practically, this happens when a Frame has its own /// journal and is hosted in NavigationWindow or another Frame.) /// internal NavigationService ParentNavigationService { get { return _parentNavigationService; } } internal bool CanReloadFromUri { get { // Special case: Doing fragment navigation within an element tree that doesn't // have a source URI. Then _currentCleanSource will be either null or something // like pack://application,,,/#fragment. (This pseudo-absolute URI is currently // malfored; that's why the complicated check below. The same situation occurs // in Navigate(uri, navState).) return !(_currentCleanSource == null || BindUriHelper.StartWithFragment(_currentCleanSource) || BindUriHelper.StartWithFragment(BindUriHelper.GetUriRelativeToPackAppBase(_currentCleanSource))); } } internal ArrayList ChildNavigationServices { get { return _childNavigationServices; } } private FinishEventHandler FinishHandler { get { if (_finishHandler == null) { _finishHandler = new FinishEventHandler(HandleFinish); } return _finishHandler; } } ////// Critical: Uses ApplicationProxyInternal.Current, which is Critical. /// Safe: Only ApplicationProxyInternal.HasTravelLogIntegration is accessed, which is okay to give out. /// private bool HasTravelLogIntegration { [SecurityCritical, SecurityTreatAsSafe] get { return IsParentedByBrowserWindow() && ApplicationProxyInternal.Current.RootBrowserWindow.HasTravelLogIntegration; } } private bool IsTopLevelContainer { get { // NavigationService should only look in the App if App exists and if // this NavigationService is on the same thread as the App. If NavService // is not on the same thread as App it means that this NavService is part of // a NavigationWindow/Frame that exists on a non-App thread and thus looking // into App to determine top level container does not make sense. return (INavigatorHost is NavigationWindow || (this.Application != null && this.Application.CheckAccess() == true && this.Application.NavService == this) ); } } private bool IsJournalLevelContainer { get { JournalNavigationScope jns = JournalScope; return jns != null && jns.RootNavigationService == this; } } private bool SandboxExternalContent { get { DependencyObject navigator = INavigatorHost as DependencyObject; if (navigator == null) return false; return (bool)navigator.GetValue(Frame.SandboxExternalContentProperty); } } internal INavigator INavigatorHost { #if DEBUG [DebuggerStepThrough] #endif get { return _navigatorHost; } set { RequestNavigateEventHandler navHandler = new RequestNavigateEventHandler(OnRequestNavigate); if (_navigatorHost != null) { IInputElement iie = _navigatorHost as IInputElement; if (iie != null) { iie.RemoveHandler(Hyperlink.RequestNavigateEvent, navHandler); } IDownloader oldDownloader = _navigatorHost as IDownloader; if (oldDownloader != null) { oldDownloader.ContentRendered -= new EventHandler(ContentRenderedHandler); } } if (value != null) { IInputElement iie = value as IInputElement; if (iie != null) { iie.AddHandler(Hyperlink.RequestNavigateEvent, navHandler); } // We want to listen to ContentRendered of the INavigatorHost so // that we can scroll into view the correct element if needed IDownloader newDownloader = value as IDownloader; if (newDownloader != null) { newDownloader.ContentRendered += new EventHandler(ContentRenderedHandler); } } _navigatorHost = value; _navigatorHostImpl = value as INavigatorImpl; } } internal NavigationStatus NavStatus { get { return _navStatus; } set { _navStatus = value; } } internal ArrayList PendingNavigationList { get { return _pendingNavigationList; } } // A new WebBrowser is created per new navigation. // At any time, an NavigationService can only have one WebBrowser; // a WebBrowser can belong to only one NavigationService. internal WebBrowser WebBrowser { get { return _webBrowser; } } internal bool IsDisposed { get { // NavigationService should only look in the App if App exists and if // this NavigationService is on the same thread as the App. If NavService // is not on the same thread as App it means that this NavService is part of // a NavigationWindow/Frame that exists on a non-App thread and thus looking // into App to determine if app is shuttind down does not make sense. bool isAppShuttingDown = false; if ((this.Application != null) && (this.Application.CheckAccess() == true) && (Application.IsShuttingDown == true)) { isAppShuttingDown = true; } return _disposed || isAppShuttingDown; } } // internal void Dispose() { _disposed = true; StopLoading(); foreach (NavigationService ns in ChildNavigationServices) { ns.Dispose(); } _journalScope = null; _bp = null; _currentSource = null; _currentCleanSource = null; _oldRootVisual = null; _childNavigationServices.Clear(); _parentNavigationService = null; _webBrowser = null; } #region Private Functions ////// NOTE: This method should be used only when the NavigationWindow is really needed. /// Normal operation should use the JournalNavigationScope (JournalScope property). /// private NavigationWindow FindNavigationWindow() { NavigationService ns = this; while (ns != null && ns.INavigatorHost != null) { NavigationWindow nw = ns.INavigatorHost as NavigationWindow; if (nw != null) return nw; ns = ns.ParentNavigationService; } return null; } static internal bool IsPageFunction(object content) { return (content as PageFunctionBase == null ? false : true); } // // The pagefunction model works by allowing listeners to attach to events before a navigation occurs. // After navigation occurs, the "caller" may be serialized - so he can't remain attached as // a listener. // // SetupPageFunctionHandlers job is to remove any listeners on the PageFunction // so these can be stored at persistence time. // // bp - the result of the Navigation, i.e. the PageFunction we're about to navigate to. // private void SetupPageFunctionHandlers(Object bp) { PageFunctionBase pf = bp as PageFunctionBase; // Frame can call this when the tree is being torn down to detach Finish handler on the PF it holds // This won't go thru the regular navigation path, so we need to detach everything here. if (bp == null) return; pf.FinishHandler = FinishHandler; // we're undoing the delegate here so that // there are no references among page functions // Since every page function has exactly one parent, // we store the info for the parent's delegate on the // pagefunction itself ReturnEventSaver saver = new ReturnEventSaver(); saver._Detach(pf); } private void HandlePageFunction(NavigateInfo navInfo) { PageFunctionBase ps = (PageFunctionBase)_bp; if (IsJournalNavigation(navInfo)) { Debug.Assert(ps._Resume); // should've been set by JournalEntryPFxx.ResumePageFunction() ps._Resume = true; } // if (ps._Resume == false) { ps.CallStart(); } else { // } } private void HandleFinish(PageFunctionBase endingPF, object ReturnEventArgs) { if (EventTrace.IsEnabled(EventTrace.Keyword.KeywordHosting, EventTrace.Level.Info)) { EventTrace.EventProvider.TraceEvent( EventTrace.Event.Wpf_NavigationPageFunctionReturn, EventTrace.Keyword.KeywordHosting, EventTrace.Level.Info, endingPF.ToString()); } // Debug.Assert(_navigateQueueItem == null, "There's a navigation pending - see kusumav for details"); // NOTE: It is not always that endingPF==_bp. A PF may end itself when its child ends. Then // HandleFinish() will be called for the grandparent PF while _bp is still the child PF. if (JournalScope == null) { throw new InvalidOperationException(SR.Get(SRID.WindowAlreadyClosed)); } Journal journal = JournalScope.Journal; PageFunctionBase parentPF = null; int parentIndex = JournalEntryPageFunction.GetParentPageJournalIndex(this, journal, endingPF); if (endingPF.RemoveFromJournal) { DoRemoveFromJournal(endingPF, parentIndex); } // If the parent page is a PF, resume it and let it know the child PF returned. // If it's not a PF, the Return event will be raised later on - see NavigateToParentPage(). if (parentIndex != _noParentPage) { JournalEntryPageFunction parentPfEntry = journal[parentIndex] as JournalEntryPageFunction; if (parentPfEntry != null) { parentPF = parentPfEntry.ResumePageFunction(); // Need to set the FinishHandler here because the PF's Return event handler // may decide to call OnReturn(). parentPF.FinishHandler = this.FinishHandler; FireChildPageFunctionReturnEvent(parentPF, endingPF, ReturnEventArgs); } } // if the parent requested a new child, don't navigate to the parent // if (_navigateQueueItem == null) { // Navigate to the Parent page. // Two cases: // Parent is a PageFunction: bParentIsPF is true, parentPF is not null. // Parent is a Non PageFunction: bParentIsPF is false, parentPF is null, // the valid info are parentIndex and ReturnEventArgs. // There may have been recursive calls into HandleFinish(). As we are unwinding here, // parentIndex may point to a journal entry that was already removed. If the parent PF // started a navigation (new or to its parent), we'd be in the 'else' case. But if that // navigation was canceled, _navigateQueueItem==null. One special case in which this // happens is when the entire "wizard" window is closed. Then NS is disposed. if (parentIndex != _noParentPage && parentIndex < journal.TotalCount && !IsDisposed) { NavigateToParentPage(endingPF, parentPF, ReturnEventArgs, parentIndex); } // } else { // The parent requested a navigation(usually to another child PF but could be a regular Xaml) // Update the parent's state in the journal // Special case: the parent PF has the RemoveFromJournal flag, and it returned to its parent. // Then parentIndex is not valid anymore. if (parentIndex < journal.TotalCount) { JournalEntryPageFunction entry = (JournalEntryPageFunction)journal[parentIndex]; entry.SaveState(parentPF); } // Return event handler should not be left attached. Debug.Assert(parentPF._Return == null); parentPF.FinishHandler = null; } } // // This method will reattach the return handler to the parent page. // and then fire the return event on the child pagefunction. // private void FireChildPageFunctionReturnEvent(object parentElem, PageFunctionBase childPF, object ReturnEventArgs) { ReturnEventSaver saver = childPF._Saver; // get the endingPF's saved info if (saver != null) { saver._Attach(parentElem, childPF); // reattach the parent to the child // When the Return event handler is invoked on the parent element, the parent is not in the tree. // But developers need to access the NavigationService from the Return event handler (to be able to // start new navigation). To make this scenario straightforward, set the NavigationService property // before raising the Return event and clear it afterwards. See details of the scenario in Dev10 bug 451875. // Similar issue with Window.GetWindow()... Window window = null; DependencyObject dobj = parentElem as DependencyObject; if ((dobj != null) && (!dobj.IsSealed)) { dobj.SetValue(NavigationServiceProperty, this); var host = this.INavigatorHost as DependencyObject; if (host != null && (window = Window.GetWindow(host)) != null) { dobj.SetValue(Window.IWindowServiceProperty, window); } } // Event handler exception continuality: if exception occurs in Return event handler, we are going to stop loading // and stop at the child pagefunction and not returning to parent. try { childPF._OnFinish(ReturnEventArgs); // then call the endingPF to fire it's event } catch { DoStopLoading(true, false); throw; } finally { saver._Detach(childPF); // now detach the event handler since we're done if ((dobj != null) && (!dobj.IsSealed)) { dobj.ClearValue(NavigationServiceProperty); if (window != null) { dobj.ClearValue(Window.IWindowServiceProperty); } } } } } ////// Deletes everything in this NavigationService from the *first* instance of the /// finishing PageFunction on. /// private void DoRemoveFromJournal(PageFunctionBase finishingChildPageFunction, int parentEntryIndex/* = -1 */) { if (!finishingChildPageFunction.RemoveFromJournal) return; bool deleting = false; Journal journal = JournalScope.Journal; int journalEntryIndex = parentEntryIndex + 1; while (journalEntryIndex < journal.TotalCount) { if (!deleting) // we haven't found the first one yet { // is this the first one? JournalEntryPageFunction journalPageFunction = journal[journalEntryIndex] as JournalEntryPageFunction; deleting = (journalPageFunction != null) && (journalPageFunction.PageFunctionId == finishingChildPageFunction.PageFunctionId); } if (deleting) { journal.RemoveEntryInternal(journalEntryIndex); } else { journalEntryIndex++; } } if (deleting) { journal.UpdateView(); // RemoveEntryInternal() doesn't do this. } else { // If the PF is not found, it simply wasn't journaled, and it must be the // current page. if (object.ReferenceEquals(_bp, finishingChildPageFunction)) { Debug.Assert(parentEntryIndex < journal.CurrentIndex); journal.ClearForwardStack(); } else { Debug.Fail("Could not find the finishing PageFunction in the journal."); } } // When the next navigation occurs (back to parent or new), the current page // (finishingChildPageFunction or another PF started by it) should not be journaled. _doNotJournalCurrentContent = true; } // Navigate to the Parent page. // Two cases: // Parent is a PageFunction: parentPF is not null. // Parent is a Non PageFunction: parentPF is null, // the valid info are parentIndex and ReturnEventArgs. // The kind of navigation depends on finishingChildPageFunction.RemoveFromJournal: // - True: then do journal navigation to the parent page (and no journal entry created // for the finishing PF) // - False: do new navigation to the parent page. private void NavigateToParentPage(PageFunctionBase finishingChildPageFunction, PageFunctionBase parentPF, object returnEventArgs, int parentIndex) { JournalEntry parentEntry = (JournalScope.Journal)[parentIndex]; if (parentPF != null) { // We shouldn't be navigating to a PageFunction that's UiLess at this stage. // By now it should have started another navigation it was delegating to a child PF. if (parentEntry.EntryType == JournalEntryType.UiLess) throw new InvalidOperationException(SR.Get(SRID.UiLessPageFunctionNotCallingOnReturn)); NavigateInfo navInfo = finishingChildPageFunction.RemoveFromJournal ? new NavigateInfo(parentEntry.Source, NavigationMode.Back, parentEntry) : new NavigateInfo(parentEntry.Source, NavigationMode.New); Navigate(parentPF, navInfo); return; } // Handle the NonPF parent page case. // Passing PageFunctionReturnInfo signals that the Return event should be raised for // the finishing child PF. PageFunctionReturnInfo pfRetInfo = finishingChildPageFunction.RemoveFromJournal ? new PageFunctionReturnInfo(finishingChildPageFunction, parentEntry.Source, NavigationMode.Back, parentEntry, returnEventArgs) : new PageFunctionReturnInfo(finishingChildPageFunction, parentEntry.Source, NavigationMode.New, null, returnEventArgs); if (parentEntry is JournalEntryUri) { this.Navigate(parentEntry.Source, pfRetInfo); } else if (parentEntry is JournalEntryKeepAlive) { object root = ((JournalEntryKeepAlive)parentEntry).KeepAliveRoot; this.Navigate(root, pfRetInfo); } else { Debug.Fail("Unhandled scenario: PageFunction returning to " + parentEntry.GetType().Name); } } // // Check if the passed object is a valid root element. // private bool IsValidRootElement(object bp) { bool isValidRoot = true; // if (AllowWindowNavigation == false && bp != null && bp is Window) { isValidRoot = false; } return isValidRoot; } #endregion Private Functions #region Events //// BPReady event // internal event BPReadyEventHandler BPReady; internal event BPReadyEventHandler PreBPReady; #endregion #region Private Properties ////// This property returns a JournalNavigationScope if available but doesn't force creating one. /// So, a Frame with JournalOwnership=Automatic for which there is no parent JNS available /// (must be rooted in something other than NavigationWindow) will not be forced to create /// its own JNS/journal. If a journal is really needed (for example, to journal a page from /// which we are navigating away), call EnsureJournal(). However, because navigator trees can be /// constructed bottom-up, most times this property should be used instead of EnsureJournal(). /// This will prevent prematurely forcing Frame to establish its own JournalNavigationScope. /// ///The tree of NavigationServices may comprise multiple JournalNavigationScopes. /// See the ParentNavigationService property. /// private JournalNavigationScope JournalScope { get { if (_journalScope == null && _navigatorHost != null) { _journalScope = _navigatorHost.GetJournal(false/*don't create*/); } return _journalScope; } } // This property indicates if this was the navigation service that initiated the navigation private bool IsNavigationInitiator { get { if (!_isNavInitiatorValid) { // If we are the top level container then we have no parent and must be the initiator of this navigation. // If we are not top level we may still be the initiator but we default to false and then query our // parent navigation service to see if it is also navigating. _isNavInitiator = IsTopLevelContainer; if (_parentNavigationService != null) { if (!_parentNavigationService.PendingNavigationList.Contains(this)) { // if the parent NavigationService doesn't contain this NavigationService object, // it means the parent NavigationService's host tree is not changed. this NavigationService // is the topmost level that a navigation was started at _isNavInitiator = true; } } // We'd like to fix the IsNavInitiator property for island frame, more details in Dev10 bug 451917. // However, it is a breaking change. In the Dev10 time frame, the breaking change bar is high. // So instead of fixing it with the right logic, we limit the scope of the change to those that matter // most - the scenario is navigation of the island Frame; the timing is after starting up. // This change does not affect startup or other initial tree construction scenarios except when the // Frame is explicitly marked to be island frame. else if (IsJournalLevelContainer) { _isNavInitiator = true; } _isNavInitiatorValid = true; } return _isNavInitiator; } } #endregion Private Properties #region Private Fields private object _bp; ///private uint _contentId; /// /// This must always be in absolute URI format (or null, for object navigation). /// If it's just fragment name, then pack://application,,,/#fragment. /// private Uri _currentSource; private Uri _currentCleanSource; private JournalEntryGroupState _journalEntryGroupState; private bool _doNotJournalCurrentContent; private bool _cancelContentRenderedHandling; ///private CustomContentState _customContentStateToSave; private CustomJournalStateInternal _rootViewerStateToSave; private WebRequest _request; private object _navState; private WebResponse _webResponse; private XamlReader _asyncObjectConverter; private bool _isNavInitiator; private bool _isNavInitiatorValid; private bool _allowWindowNavigation; private Guid _guidId = Guid.Empty; private INavigator _navigatorHost; private INavigatorImpl _navigatorHostImpl; /// /// Cached reference to the applicable JNS. Normally, should not be accessed directly. /// See the JournalScope property. /// private JournalNavigationScope _journalScope; private ArrayList _childNavigationServices = new ArrayList(2); private NavigationService _parentNavigationService; private bool _disposed; // IUI-specific data private FinishEventHandler _finishHandler; private NavigationStatus _navStatus = NavigationStatus.Idle; // // The next group of variables hold state for the pending navigation // // Contains a list of child frames that are still being loaded private ArrayList _pendingNavigationList = new ArrayList(2); // Contains a list of recursive navigate items, last one in the list will supercede // (see comments in HandleNavigating and DoStopLoading) private ArrayList _recursiveNavigateList = new ArrayList(2); // Navigation currently in progress (either waiting for DispatcherOperation to be invoked or being actively downloaded) private NavigateQueueItem _navigateQueueItem; private long _bytesRead; private long _maxBytes; private Visual _oldRootVisual; private const int _noParentPage = -1; // #if DEBUG_CLR_MEM private static int _navigationCLRPass = 0; #endif private WebBrowser _webBrowser; #endregion Private Fields } #endregion NavigationService Class #region public Delegates ////// Delegate for the Navigating event /// public delegate void NavigatingCancelEventHandler(Object sender, NavigatingCancelEventArgs e); ////// Delegate for the NavigationProgress event /// public delegate void NavigationProgressEventHandler(Object sender, NavigationProgressEventArgs e); ////// Delegate for the NavigationFailed event /// /// /// public delegate void NavigationFailedEventHandler(Object sender, NavigationFailedEventArgs e); ////// Delegate for the Navigated event /// public delegate void NavigatedEventHandler(Object sender, NavigationEventArgs e); ////// Delegate for the LoadCompleted event /// public delegate void LoadCompletedEventHandler(Object sender, NavigationEventArgs e); ////// Delegate for the NavigationStopped event /// public delegate void NavigationStoppedEventHandler(Object sender, NavigationEventArgs e); ////// Delegate for FragmentNavigation event /// /// /// public delegate void FragmentNavigationEventHandler(object sender, FragmentNavigationEventArgs e); #endregion public Delegates #region internal Delegates internal delegate void BPReadyEventHandler(Object sender, BPReadyEventArgs e); internal delegate void FinishEventHandler(PageFunctionBase sender, object ReturnEventArgs); #endregion internal Delegates #region internal class #region RequestState class internal class RequestState { internal RequestState(WebRequest request, Uri source, Object navState, Dispatcher callbackDispatcher) { _request = request; _source = source; _navState = navState; _callbackDispatcher = callbackDispatcher; } internal WebRequest Request { get { return _request; } } internal Uri Source { get { return _source; } } internal Object NavState { get { return _navState; } } internal Dispatcher CallbackDispatcher { get { return _callbackDispatcher; } } private WebRequest _request; private Uri _source; private Object _navState; private Dispatcher _callbackDispatcher; } #endregion RequestState class #region BPReadyEventArgs Class //// EventArgs for BPReady events // internal class BPReadyEventArgs : CancelEventArgs { //// constructor // internal BPReadyEventArgs(Object content, Uri uri) : base() { _content = content; _uri = uri; } //// property for Root // internal Object Content { get { return _content; } } internal Uri Uri { get { return _uri; } } Object _content; Uri _uri; } #endregion BPReadyEventArgs Class #region NavigateInfo class internal class NavigateInfo { internal NavigateInfo(Uri source) { _source = source; } internal NavigateInfo(Uri source, NavigationMode navigationMode) { _source = source; _navigationMode = navigationMode; } internal NavigateInfo(Uri source, NavigationMode navigationMode, JournalEntry journalEntry) { _source = source; _navigationMode = navigationMode; _journalEntry = journalEntry; } internal Uri Source { get { return _source; } } internal NavigationMode NavigationMode { #if DEBUG [DebuggerStepThrough] #endif get { return _navigationMode; } } internal JournalEntry JournalEntry { #if DEBUG [DebuggerStepThrough] #endif get { return _journalEntry; } } ////// Assumption: For new navigations, there is no preexisting journal entry to go back to. /// For Back/Fwd, there must be an existing entry. /// internal bool IsConsistent { get { return (_navigationMode == NavigationMode.New ^ _journalEntry != null) || _navigationMode == NavigationMode.Refresh; } } // Uri is only used for Navigate(object) codepaths to pass the pending source for Startup Uri and // KeepAlive journal navigations which have a Uri associated with it though we are navigating // by content trees private Uri _source; private NavigationMode _navigationMode = NavigationMode.New; private JournalEntry _journalEntry; } #endregion NavigateInfo class #region PageFunctionReturnInfo class // // This NavigateInfo is only used in the below case : // The child PageFunction is done, and the parent page is not a PageFunction. // In the FinishHandler, it needs to navigate to the parent, this NavigationInfo // is passed at that moment. // internal class PageFunctionReturnInfo : NavigateInfo { internal PageFunctionReturnInfo(PageFunctionBase finishingChildPageFunction, Uri source, NavigationMode navigationMode, JournalEntry journalEntry, object returnEventArgs) : base(source, navigationMode, journalEntry) { _returnEventArgs = returnEventArgs; _finishingChildPageFunction = finishingChildPageFunction; } internal object ReturnEventArgs { get { return _returnEventArgs; } } internal PageFunctionBase FinishingChildPageFunction { get { return _finishingChildPageFunction; } } private object _returnEventArgs; private PageFunctionBase _finishingChildPageFunction; } #endregion PageFunctionReturnInfo class #region NavigateQueueItem class internal class NavigateQueueItem { internal NavigateQueueItem(Uri source, object content, NavigationMode mode, Object navState, NavigationService nc) { _source = source; _content = content; _navState = navState; _nc = nc; _navigationMode = mode; } #if DEBUG internal bool IsPosted { get { return _postedOp != null; } } #endif internal void PostNavigation() { Debug.Assert(_postedOp == null); _postedOp = Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new DispatcherOperationCallback(this.Dispatch), null); } internal void Stop() { // Stop all pending navigations - ones that have been posted but not dispatched yet // and the ones in progress. // Abort dispatched navigation operations if (_postedOp != null) { _postedOp.Abort(); _postedOp = null; } } internal Uri Source { get { return _source; } } internal object NavState { get { return _navState; } } private object Dispatch(object obj) { _postedOp = null; // The second check is to cover null content/null source navigations. // Null source navigation will be transformed to a null content navigation since we // cannot bind to a null source. if (_content != null || _source == null) { _nc.DoNavigate(_content, _navigationMode, _navState); } else { _nc.DoNavigate(_source, _navigationMode, _navState); } return null; } Uri _source; object _content; Object _navState; NavigationService _nc; NavigationMode _navigationMode = NavigationMode.New; DispatcherOperation _postedOp; } #endregion NavigateQueueItem class #region DisposeTreeQueueItem class /// This class walks the logical tree. We don't need to walk the visual tree /// since Visuals don't need to be explicitly disposed now. internal class DisposeTreeQueueItem { internal object Dispatch(object o) { this.DisposeElement(_root); return null; } ////// Dispose the elements in the tree, children first. /// /// The node to dispose. internal void DisposeElement(Object node) { DependencyObject dobj = node as DependencyObject; if (dobj != null) { bool hasChildren = false; IEnumerator children = LogicalTreeHelper.GetLogicalChildren(dobj); if (children != null) { // Recurse into each child while (children.MoveNext()) { hasChildren = true; object child = children.Current; Debug.Assert(child != null); DisposeElement(child); } } if (!hasChildren) { // This case is needed specifically for Frame when it has WebControl in it. (1521096) // Frame.Content is not exposed as a logical child of Frame. ContentControl cc = dobj as ContentControl; if (cc != null && cc.ContentIsNotLogical && cc.Content != null) { DisposeElement(cc.Content); } } } // Now that we've recursed through all descendants, dispose this node if it needs it IDisposable disposable = node as IDisposable; if (disposable != null) { disposable.Dispose(); } } internal DisposeTreeQueueItem(Object node) { Debug.Assert(node != null, "Trying to dispose a null Logical Tree Node"); _root = node; } private Object _root; } #endregion DisposeTreeQueueItem class #endregion internal class } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TileBrush.cs
- TextTreeUndo.cs
- SingleStorage.cs
- EntityUtil.cs
- SecurityAttributeGenerationHelper.cs
- TextClipboardData.cs
- EdmProperty.cs
- FixedSOMPageElement.cs
- OrderedDictionary.cs
- EmbeddedMailObjectsCollection.cs
- KnownAssemblyEntry.cs
- ObjectDataSourceMethodEventArgs.cs
- TreeNodeCollection.cs
- CaseStatement.cs
- baseaxisquery.cs
- InkCollectionBehavior.cs
- JumpPath.cs
- GeneralTransform2DTo3DTo2D.cs
- ToolStripControlHost.cs
- DataSourceSelectArguments.cs
- PropertyKey.cs
- ImageMap.cs
- ObjectListFieldsPage.cs
- RootBuilder.cs
- updateconfighost.cs
- RecipientServiceModelSecurityTokenRequirement.cs
- CustomPopupPlacement.cs
- PointConverter.cs
- NetTcpBinding.cs
- AnnotationResource.cs
- FormatException.cs
- LogicalTreeHelper.cs
- rsa.cs
- TraceUtility.cs
- BitmapEffectDrawingContent.cs
- SynchronizedRandom.cs
- DoubleLink.cs
- MarshalByValueComponent.cs
- DesignTimeParseData.cs
- IApplicationTrustManager.cs
- OrderingInfo.cs
- ResourceManagerWrapper.cs
- Attributes.cs
- RadialGradientBrush.cs
- SingleConverter.cs
- Application.cs
- ObjectConverter.cs
- WebResponse.cs
- ProxyWebPartManager.cs
- GuidelineCollection.cs
- CurrencyWrapper.cs
- ImageMetadata.cs
- DSASignatureDeformatter.cs
- recordstatescratchpad.cs
- OracleCommandSet.cs
- SizeAnimationClockResource.cs
- RelatedEnd.cs
- WeakKeyDictionary.cs
- SerializerProvider.cs
- HandlerMappingMemo.cs
- isolationinterop.cs
- Win32SafeHandles.cs
- AutomationPatternInfo.cs
- ContextItemManager.cs
- ScrollableControl.cs
- TouchEventArgs.cs
- EventListenerClientSide.cs
- ContainerFilterService.cs
- AVElementHelper.cs
- Rules.cs
- DtrList.cs
- Floater.cs
- XmlAttributes.cs
- ThreadPoolTaskScheduler.cs
- HttpWebResponse.cs
- _AcceptOverlappedAsyncResult.cs
- HttpCookiesSection.cs
- WebContext.cs
- mediapermission.cs
- HtmlTable.cs
- WindowsPrincipal.cs
- TextContainer.cs
- CodeValidator.cs
- FixedSOMTextRun.cs
- SudsWriter.cs
- UnknownWrapper.cs
- BitmapVisualManager.cs
- WebBrowserSiteBase.cs
- Facet.cs
- SyndicationCategory.cs
- ReadOnlyAttribute.cs
- ConnectionStringEditor.cs
- StatementContext.cs
- RangeValueProviderWrapper.cs
- LogWriteRestartAreaState.cs
- CheckBoxField.cs
- CancelEventArgs.cs
- _NegoState.cs
- translator.cs
- MD5HashHelper.cs