private bool IsJournalNavigation(NavigateInfo navInfo) { return navInfo != null && (navInfo.NavigationMode == NavigationMode.Back || navInfo.NavigationMode == NavigationMode.Forward); }
/// <summary> /// 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. /// </summary> /// <remarks> /// 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. /// </remarks> /// <SecurityNote> /// 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. /// </SecurityNote> 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; }
bool IsConsistent(NavigateInfo navInfo) { return navInfo == null || navInfo.IsConsistent && (navInfo.JournalEntry == null || navInfo.JournalEntry.NavigationServiceId == _guidId); }
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 { // } }
// // 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; }
/// <returns> whether to continue with committing the navigation to the new content </returns> 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; }
/// <returns> 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(). /// </returns> 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; }