// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/render_view_host_manager.h" #include "base/command_line.h" #include "base/logging.h" #include "chrome/browser/interstitial_page_delegate.h" #include "chrome/browser/navigation_controller.h" #include "chrome/browser/navigation_entry.h" #include "chrome/browser/render_widget_host_view.h" #include "chrome/browser/render_view_host.h" #include "chrome/browser/render_view_host_delegate.h" #include "chrome/browser/site_instance.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/notification_service.h" // Destroys the given |**render_view_host| and NULLs out |*render_view_host| // and then NULLs the field. Callers should only pass pointers to the // pending_render_view_host_, interstitial_render_view_host_, or // original_render_view_host_ fields of this object. static void CancelRenderView(RenderViewHost** render_view_host) { (*render_view_host)->Shutdown(); (*render_view_host) = NULL; } RenderViewHostManager::RenderViewHostManager( RenderViewHostFactory* render_view_factory, RenderViewHostDelegate* render_view_delegate, Delegate* delegate) : delegate_(delegate), renderer_state_(NORMAL), render_view_factory_(render_view_factory), render_view_delegate_(render_view_delegate), render_view_host_(NULL), original_render_view_host_(NULL), interstitial_render_view_host_(NULL), pending_render_view_host_(NULL), interstitial_delegate_(NULL), showing_repost_interstitial_(false) { } RenderViewHostManager::~RenderViewHostManager() { // Shutdown should have been called which should have cleaned these up. DCHECK(!render_view_host_); DCHECK(!pending_render_view_host_); DCHECK(!original_render_view_host_); DCHECK(!interstitial_render_view_host_); } void RenderViewHostManager::Init(Profile* profile, SiteInstance* site_instance, int routing_id, HANDLE modal_dialog_event) { // Create a RenderViewHost, once we have an instance. It is important to // immediately give this SiteInstance to a RenderViewHost so that it is // ref counted. if (!site_instance) site_instance = SiteInstance::CreateSiteInstance(profile); render_view_host_ = CreateRenderViewHost( site_instance, routing_id, modal_dialog_event); } void RenderViewHostManager::Shutdown() { if (showing_interstitial_page()) { // The tab is closed while the interstitial page is showing, hide and // destroy it. HideInterstitialPage(false, false); } DCHECK(!interstitial_render_view_host_) << "Should have been deleted by Hide"; if (pending_render_view_host_) { pending_render_view_host_->Shutdown(); pending_render_view_host_ = NULL; } if (original_render_view_host_) { original_render_view_host_->Shutdown(); original_render_view_host_ = NULL; } // We should always have a main RenderViewHost. render_view_host_->Shutdown(); render_view_host_ = NULL; } RenderViewHost* RenderViewHostManager::Navigate(const NavigationEntry& entry) { RenderViewHost* dest_render_view_host = UpdateRendererStateNavigate(entry); if (!dest_render_view_host) return NULL; // We weren't able to create a pending render view host. // If the current render_view_host_ isn't live, we should create it so // that we don't show a sad tab while the dest_render_view_host fetches // its first page. (Bug 1145340) if (dest_render_view_host != render_view_host_ && !render_view_host_->IsRenderViewLive()) { delegate_->CreateRenderViewForRenderManager(render_view_host_); } // If the renderer crashed, then try to create a new one to satisfy this // navigation request. if (!dest_render_view_host->IsRenderViewLive()) { if (!delegate_->CreateRenderViewForRenderManager(dest_render_view_host)) return NULL; // Now that we've created a new renderer, be sure to hide it if it isn't // our primary one. Otherwise, we might crash if we try to call Show() // on it later. if (dest_render_view_host != render_view_host_ && dest_render_view_host->view()) { dest_render_view_host->view()->Hide(); } else { // This is our primary renderer, notify here as we won't be calling // SwapToRenderView (which does the notify). RenderViewHostSwitchedDetails details; details.new_host = render_view_host_; details.old_host = NULL; NotificationService::current()->Notify( NOTIFY_RENDER_VIEW_HOST_CHANGED, Source( delegate_->GetControllerForRenderManager()), Details(&details)); } } showing_repost_interstitial_ = false; return dest_render_view_host; } void RenderViewHostManager::Stop() { render_view_host_->Stop(); // If we aren't in the NORMAL renderer state, we should stop the pending // renderers. This will lead to a DidFailProvisionalLoad, which will // properly destroy them. if (renderer_state_ == PENDING) { pending_render_view_host_->Stop(); } else if (renderer_state_ == ENTERING_INTERSTITIAL) { interstitial_render_view_host_->Stop(); if (pending_render_view_host_) { pending_render_view_host_->Stop(); } } else if (renderer_state_ == LEAVING_INTERSTITIAL) { if (pending_render_view_host_) { pending_render_view_host_->Stop(); } } } void RenderViewHostManager::SetIsLoading(bool is_loading) { render_view_host_->SetIsLoading(is_loading); if (pending_render_view_host_) pending_render_view_host_->SetIsLoading(is_loading); if (original_render_view_host_) original_render_view_host_->SetIsLoading(is_loading); } void RenderViewHostManager::DidNavigateMainFrame( RenderViewHost* render_view_host) { if (renderer_state_ == NORMAL) { // We should only hear this from our current renderer. DCHECK(render_view_host == render_view_host_); return; } else if (renderer_state_ == PENDING) { if (render_view_host == pending_render_view_host_) { // The pending cross-site navigation completed, so show the renderer. SwapToRenderView(&pending_render_view_host_, true); renderer_state_ = NORMAL; } else if (render_view_host == render_view_host_) { // A navigation in the original page has taken place. Cancel the pending // one. CancelRenderView(&pending_render_view_host_); renderer_state_ = NORMAL; } else { // No one else should be sending us DidNavigate in this state. DCHECK(false); return; } } else if (renderer_state_ == ENTERING_INTERSTITIAL) { if (render_view_host == interstitial_render_view_host_) { // The interstitial renderer is ready, so show it, and keep the old // RenderViewHost around. original_render_view_host_ = render_view_host_; SwapToRenderView(&interstitial_render_view_host_, false); renderer_state_ = INTERSTITIAL; } else if (render_view_host == render_view_host_) { // We shouldn't get here, because the original render view was the one // that caused the ShowInterstitial. // However, until we intercept navigation events from JavaScript, it is // possible to get here, if another tab tells render_view_host_ to // navigate. To be safe, we'll cancel the interstitial and show the // page that caused the DidNavigate. CancelRenderView(&interstitial_render_view_host_); if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); renderer_state_ = NORMAL; } else if (render_view_host == pending_render_view_host_) { // We shouldn't get here, because the original render view was the one // that caused the ShowInterstitial. // However, until we intercept navigation events from JavaScript, it is // possible to get here, if another tab tells pending_render_view_host_ // to navigate. To be safe, we'll cancel the interstitial and show the // page that caused the DidNavigate. CancelRenderView(&interstitial_render_view_host_); SwapToRenderView(&pending_render_view_host_, true); renderer_state_ = NORMAL; } else { // No one else should be sending us DidNavigate in this state. DCHECK(false); return; } } else if (renderer_state_ == INTERSTITIAL) { if (render_view_host == original_render_view_host_) { // We shouldn't get here, because the original render view was the one // that caused the ShowInterstitial. // However, until we intercept navigation events from JavaScript, it is // possible to get here, if another tab tells render_view_host_ to // navigate. To be safe, we'll cancel the interstitial and show the // page that caused the DidNavigate. SwapToRenderView(&original_render_view_host_, true); if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); renderer_state_ = NORMAL; } else if (render_view_host == pending_render_view_host_) { // No one else should be sending us DidNavigate in this state. // However, until we intercept navigation events from JavaScript, it is // possible to get here, if another tab tells pending_render_view_host_ // to navigate. To be safe, we'll cancel the interstitial and show the // page that caused the DidNavigate. SwapToRenderView(&pending_render_view_host_, true); CancelRenderView(&original_render_view_host_); renderer_state_ = NORMAL; } else { // No one else should be sending us DidNavigate in this state. DCHECK(false); return; } InterstitialPageGone(); } else if (renderer_state_ == LEAVING_INTERSTITIAL) { if (render_view_host == original_render_view_host_) { // We navigated to something in the original renderer, so show it. if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); SwapToRenderView(&original_render_view_host_, true); renderer_state_ = NORMAL; } else if (render_view_host == pending_render_view_host_) { // We navigated to something in the pending renderer. CancelRenderView(&original_render_view_host_); SwapToRenderView(&pending_render_view_host_, true); renderer_state_ = NORMAL; } else { // No one else should be sending us DidNavigate in this state. DCHECK(false); return; } InterstitialPageGone(); } else { // No such state. DCHECK(false); return; } } void RenderViewHostManager::OnCrossSiteResponse(int new_render_process_host_id, int new_request_id) { // Should only see this while we have a pending renderer, possibly during an // interstitial. Otherwise, we should ignore. if (renderer_state_ != PENDING && renderer_state_ != LEAVING_INTERSTITIAL) return; DCHECK(pending_render_view_host_); // Tell the old renderer to run its onunload handler. When it finishes, it // will send a ClosePage_ACK to the ResourceDispatcherHost with the given // IDs (of the pending RVH's request), allowing the pending RVH's response to // resume. if (showing_interstitial_page()) { DCHECK(original_render_view_host_); original_render_view_host_->ClosePage(new_render_process_host_id, new_request_id); } else { render_view_host_->ClosePage(new_render_process_host_id, new_request_id); } // ResourceDispatcherHost has told us to run the onunload handler, which // means it is not a download or unsafe page, and we are going to perform the // navigation. Thus, we no longer need to remember that the RenderViewHost // is part of a pending cross-site request. pending_render_view_host_->SetHasPendingCrossSiteRequest(false); } void RenderViewHostManager::RendererAbortedProvisionalLoad( RenderViewHost* render_view_host) { // We used to cancel the pending renderer here for cross-site downloads. // However, it's not safe to do that because the download logic repeatedly // looks for this TabContents based on a render view ID. Instead, we just // leave the pending renderer around until the next navigation event // (Navigate, DidNavigate, etc), which will clean it up properly. // TODO(creis): All of this will go away when we move the cross-site logic // to ResourceDispatcherHost, so that we intercept responses rather than // navigation events. (That's necessary to support onunload anyway.) Once // we've made that change, we won't create a pending renderer until we know // the response is not a download. if (renderer_state_ == ENTERING_INTERSTITIAL) { if ((pending_render_view_host_ && (pending_render_view_host_ == render_view_host)) || (!pending_render_view_host_ && (render_view_host_ == render_view_host))) { // The abort came from the RenderViewHost that triggered the // interstitial. (e.g., User clicked stop after ShowInterstitial but // before the interstitial was visible.) We should go back to NORMAL. // Note that this is an uncommon case, because we are only in the // ENTERING_INTERSTITIAL state in the small time window while the // interstitial's RenderViewHost is being created. if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); CancelRenderView(&interstitial_render_view_host_); renderer_state_ = NORMAL; } // We can get here, at least in the following case. // We show an interstitial, then navigate to a URL that leads to another // interstitial. Now there's a race. The new interstitial will be // created and we will go to ENTERING_INTERSTITIAL, but the old one will // meanwhile destroy itself and fire DidFailProvisionalLoad. That puts // us here. Should be safe to ignore the DidFailProvisionalLoad, from // the perspective of the renderer state. } else if (renderer_state_ == LEAVING_INTERSTITIAL) { // If we've left the interstitial by seeing a download (or otherwise // aborting a load), we should get back to the original page, because // interstitial page doesn't make sense anymore. (For example, we may // have clicked Proceed on a download URL.) // TODO(creis): This causes problems in the old process model when // visiting a new URL from an interstitial page. // This is because we receive a DidFailProvisionalLoad from cancelling // the first request, which is indistinguishable from a // DidFailProvisionalLoad from the second request (if it is a download). // We need to find a way to distinguish these cases, because it doesn't // make sense to keep showing the interstitial after a download. // if (pending_render_view_host_) // CancelRenderView(&pending_render_view_host_); // SwapToRenderView(&original_render_view_host_, true); // renderer_state_ = NORMAL; // delegate_->InterstitialPageGoneFromRenderManager(); } } void RenderViewHostManager::ShouldClosePage(bool proceed) { // Should only see this while we have a pending renderer. Otherwise, we // should ignore. if (!pending_render_view_host_) { bool proceed_to_fire_unload; delegate_->BeforeUnloadFiredFromRenderManager(proceed, &proceed_to_fire_unload); if (proceed_to_fire_unload) { // This is not a cross-site navigation, the tab is being closed. render_view_host_->FirePageUnload(); } return; } DCHECK(renderer_state_ != ENTERING_INTERSTITIAL); DCHECK(renderer_state_ != INTERSTITIAL); if (proceed) { // Ok to unload the current page, so proceed with the cross-site navigate. pending_render_view_host_->SetNavigationsSuspended(false); } else { // Current page says to cancel. CancelRenderView(&pending_render_view_host_); renderer_state_ = NORMAL; } } void RenderViewHostManager::ShowInterstitialPage( const std::string& html_text, InterstitialPageDelegate* delegate) { // Note that it is important that the interstitial page render view host is // in the same process as the normal render view host for the tab, so they // use page ids from the same pool. If they came from different processes, // page ids may collide causing confusion in the controller (existing // navigation entries in the controller history could get overridden with the // interstitial entry). SiteInstance* interstitial_instance = NULL; if (renderer_state_ == NORMAL) { // render_view_host_ will not be deleted before the end of this method, so // we don't have to worry about this SiteInstance's ref count dropping to // zero. interstitial_instance = render_view_host_->site_instance(); } else if (renderer_state_ == PENDING) { // pending_render_view_host_ will not be deleted before the end of this // method (when we are in this state), so we don't have to worry about this // SiteInstance's ref count dropping to zero. interstitial_instance = pending_render_view_host_->site_instance(); } else if (renderer_state_ == ENTERING_INTERSTITIAL) { // We should never get here if we're in the process of showing an // interstitial. // However, until we intercept navigation events from JavaScript, it is // possible to get here, if another tab tells render_view_host_ to // navigate to a URL that causes an interstitial. To be safe, we'll cancel // the first interstitial. CancelRenderView(&interstitial_render_view_host_); renderer_state_ = NORMAL; // We'd like to now show the new interstitial, but if there's a // pending_render_view_host_, we can't tell if this JavaScript navigation // occurred in the original or the pending renderer. That means we won't // know where to proceed, so we can't show the interstitial. This is // really just meant to avoid a crash until we can intercept JavaScript // navigation events, so for now we'll kill the interstitial and go back // to the last known good page. if (pending_render_view_host_) { CancelRenderView(&pending_render_view_host_); return; } // Should be safe to show the interstitial for the new page. // render_view_host_ will not be deleted before the end of this method, so // we don't have to worry about this SiteInstance's ref count dropping to // zero. interstitial_instance = render_view_host_->site_instance(); } else if (renderer_state_ == INTERSTITIAL) { // We should never get here if we're already showing an interstitial. // However, until we intercept navigation events from JavaScript, it is // possible to get here, if another tab tells render_view_host_ to // navigate to a URL that causes an interstitial. To be safe, we'll go // back to normal first. if (pending_render_view_host_ != NULL) { // There was a pending RVH. We don't know which RVH caused this call // to ShowInterstitial, so we can't really proceed. We'll have to stay // in the NORMAL state, showing the last good page. This is only a // temporary fix anyway, to stave off a crash. HideInterstitialPage(false, false); return; } // Should be safe to show the interstitial for the new page. // render_view_host_ will not be deleted before the end of this method, so // we don't have to worry about this SiteInstance's ref count dropping to // zero. SwapToRenderView(&original_render_view_host_, true); interstitial_instance = render_view_host_->site_instance(); } else if (renderer_state_ == LEAVING_INTERSTITIAL) { SwapToRenderView(&original_render_view_host_, true); interstitial_instance = NULL; if (pending_render_view_host_) { // We're now effectively in PENDING. // pending_render_view_host_ will not be deleted before the end of this // method, so we don't have to worry about this SiteInstance's ref count // dropping to zero. interstitial_instance = pending_render_view_host_->site_instance(); } else { // We're now effectively in NORMAL. // render_view_host_ will not be deleted before the end of this method, // so we don't have to worry about this SiteInstance's ref count dropping // to zero. interstitial_instance = render_view_host_->site_instance(); } } else { // No such state. DCHECK(false); return; } // Create a pending renderer and move to ENTERING_INTERSTITIAL. interstitial_render_view_host_ = CreateRenderViewHost(interstitial_instance, MSG_ROUTING_NONE, NULL); interstitial_delegate_ = delegate; bool success = delegate_->CreateRenderViewForRenderManager( interstitial_render_view_host_); if (!success) { // TODO(creis): If this fails, should we load the interstitial in // render_view_host_? We shouldn't just skip the interstitial... CancelRenderView(&interstitial_render_view_host_); return; } // Don't show the view yet. interstitial_render_view_host_->view()->Hide(); renderer_state_ = ENTERING_INTERSTITIAL; // We allow the DOM bindings as a way to get the page to talk back to us. interstitial_render_view_host_->AllowDomAutomationBindings(); interstitial_render_view_host_->LoadAlternateHTMLString(html_text, false, GURL::EmptyGURL(), std::string()); } void RenderViewHostManager::HideInterstitialPage(bool wait_for_navigation, bool proceed) { if (renderer_state_ == NORMAL || renderer_state_ == PENDING) { // Shouldn't get here, since there's no interstitial showing. DCHECK(false); return; } else if (renderer_state_ == ENTERING_INTERSTITIAL) { // Unclear if it is possible to get here. (Can you hide the interstitial // before it is shown?) If so, we should go back to NORMAL. CancelRenderView(&interstitial_render_view_host_); if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); renderer_state_ = NORMAL; return; } DCHECK(showing_interstitial_page()); DCHECK(render_view_host_ && original_render_view_host_ && !interstitial_render_view_host_); if (renderer_state_ == INTERSTITIAL) { // Disable the Proceed button on the interstitial, because the destination // renderer might get replaced. DisableInterstitialProceed(false); } else if (renderer_state_ == LEAVING_INTERSTITIAL) { // We have already given up the ability to proceed by starting a new // navigation. If this is a request to proceed, we must ignore it. // (Hopefully we will have disabled the Proceed button by now, but it's // possible to get here before that happens.) if (proceed) return; } if (wait_for_navigation) { // We are resuming the loading. We need to set the state to loading again // as it was set to false when the interstitial stopped loading (so the // throbber runs). delegate_->DidStartLoadingFromRenderManager(render_view_host_, NULL); } if (proceed) { // Now we will resume loading automatically, either in // original_render_view_host_ or in pending_render_view_host_. When it // completes, we will display the renderer in DidNavigate. renderer_state_ = LEAVING_INTERSTITIAL; } else { // Don't proceed. Go back to the previously showing page. if (renderer_state_ == LEAVING_INTERSTITIAL) { // We said DontProceed after starting to leave the interstitial. // Abandon whatever we were in the process of doing. original_render_view_host_->Stop(); } SwapToRenderView(&original_render_view_host_, true); if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); renderer_state_ = NORMAL; InterstitialPageGone(); } } bool RenderViewHostManager::IsRenderViewInterstitial( const RenderViewHost* render_view_host) const { if (showing_interstitial_page()) return render_view_host_ == render_view_host; if (renderer_state_ == ENTERING_INTERSTITIAL) return interstitial_render_view_host_ == render_view_host; return false; } void RenderViewHostManager::OnJavaScriptMessageBoxClosed( IPC::Message* reply_msg, bool success, const std::wstring& prompt) { RenderViewHost* rvh = render_view_host_; if (showing_interstitial_page()) { // No JavaScript message boxes are ever shown by interstitial pages, but // they can be shown by the original RVH while an interstitial page is // showing (e.g., from an onunload event handler). We should send this to // the original RVH and not the interstitial's RVH. // TODO(creis): Perhaps the JavascriptMessageBoxHandler should store which // RVH created it, so that it can tell this method which RVH to reply to. DCHECK(original_render_view_host_); rvh = original_render_view_host_; } rvh->JavaScriptMessageBoxClosed(reply_msg, success, prompt); } bool RenderViewHostManager::ShouldTransitionCrossSite() { // True if we are using process-per-site-instance (default) or // process-per-site (kProcessPerSite). return !CommandLine().HasSwitch(switches::kProcessPerTab); } SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry( const NavigationEntry& entry, SiteInstance* curr_instance) { // NOTE: This is only called when ShouldTransitionCrossSite is true. // If the entry has an instance already, we should use it. if (entry.site_instance()) return entry.site_instance(); // (UGLY) HEURISTIC, process-per-site only: // // If this navigation is generated, then it probably corresponds to a search // query. Given that search results typically lead to users navigating to // other sites, we don't really want to use the search engine hostname to // determine the site instance for this navigation. // // NOTE: This can be removed once we have a way to transition between // RenderViews in response to a link click. // if (CommandLine().HasSwitch(switches::kProcessPerSite) && entry.transition_type() == PageTransition::GENERATED) return curr_instance; const GURL& dest_url = entry.url(); // If we haven't used our SiteInstance (and thus RVH) yet, then we can use it // for this entry. We won't commit the SiteInstance to this site until the // navigation commits (in DidNavigate), unless the navigation entry was // restored. As session restore loads all the pages immediately we need to set // the site first, otherwise after a restore none of the pages would share // renderers. if (!curr_instance->has_site()) { // If we've already created a SiteInstance for our destination, we don't // want to use this unused SiteInstance; use the existing one. (We don't // do this check if the curr_instance has a site, because for now, we want // to compare against the current URL and not the SiteInstance's site. In // this case, there is no current URL, so comparing against the site is ok. // See additional comments below.) if (curr_instance->HasRelatedSiteInstance(dest_url)) { return curr_instance->GetRelatedSiteInstance(dest_url); } else { if (entry.restored()) curr_instance->SetSite(dest_url); return curr_instance; } } // Otherwise, only create a new SiteInstance for cross-site navigation. // TODO(creis): Once we intercept links and script-based navigations, we // will be able to enforce that all entries in a SiteInstance actually have // the same site, and it will be safe to compare the URL against the // SiteInstance's site, as follows: // const GURL& current_url = curr_instance->site(); // For now, though, we're in a hybrid model where you only switch // SiteInstances if you type in a cross-site URL. This means we have to // compare the entry's URL to the last committed entry's URL. NavigationController* controller = delegate_->GetControllerForRenderManager(); NavigationEntry* curr_entry = controller->GetLastCommittedEntry(); if (showing_interstitial_page()) { // The interstitial is currently the last committed entry, but we want to // compare against the last non-interstitial entry. curr_entry = controller->GetEntryAtOffset(-1); } // If there is no last non-interstitial entry (and curr_instance already // has a site), then we must have been opened from another tab. We want // to compare against the URL of the page that opened us, but we can't // get to it directly. The best we can do is check against the site of // the SiteInstance. This will be correct when we intercept links and // script-based navigations, but for now, it could place some pages in a // new process unnecessarily. We should only hit this case if a page tries // to open a new tab to an interstitial-inducing URL, and then navigates // the page to a different same-site URL. (This seems very unlikely in // practice.) const GURL& current_url = (curr_entry) ? curr_entry->url() : curr_instance->site(); if (SiteInstance::IsSameWebSite(current_url, dest_url)) { return curr_instance; } else { // Start the new renderer in a new SiteInstance, but in the current // BrowsingInstance. It is important to immediately give this new // SiteInstance to a RenderViewHost (if it is different than our current // SiteInstance), so that it is ref counted. This will happen in // CreatePendingRenderView. return curr_instance->GetRelatedSiteInstance(dest_url); } } bool RenderViewHostManager::CreatePendingRenderView(SiteInstance* instance) { NavigationEntry* curr_entry = delegate_->GetControllerForRenderManager()->GetLastCommittedEntry(); if (curr_entry && curr_entry->tab_type() == TAB_CONTENTS_WEB) { DCHECK(!curr_entry->content_state().empty()); // TODO(creis): Should send a message to the RenderView to let it know // we're about to switch away, so that it sends an UpdateState message. } pending_render_view_host_ = CreateRenderViewHost(instance, MSG_ROUTING_NONE, NULL); bool success = delegate_->CreateRenderViewForRenderManager( pending_render_view_host_); if (success) { // Don't show the view until we get a DidNavigate from it. pending_render_view_host_->view()->Hide(); } else { CancelRenderView(&pending_render_view_host_); } return success; } RenderViewHost* RenderViewHostManager::CreateRenderViewHost( SiteInstance* instance, int routing_id, HANDLE modal_dialog_event) { if (render_view_factory_) { return render_view_factory_->CreateRenderViewHost( instance, render_view_delegate_, routing_id, modal_dialog_event); } else { return new RenderViewHost(instance, render_view_delegate_, routing_id, modal_dialog_event); } } void RenderViewHostManager::SwapToRenderView( RenderViewHost** new_render_view_host, bool destroy_after) { // Remember if the page was focused so we can focus the new renderer in // that case. bool focus_render_view = render_view_host_->view() && render_view_host_->view()->HasFocus(); // Hide the current view and prepare to destroy it. // TODO(creis): Get the old RenderViewHost to send us an UpdateState message // before we destroy it. if (render_view_host_->view()) render_view_host_->view()->Hide(); RenderViewHost* old_render_view_host = render_view_host_; // Swap in the pending view and make it active. render_view_host_ = (*new_render_view_host); (*new_render_view_host) = NULL; // If the view is gone, then this RenderViewHost died while it was hidden. // We ignored the RendererGone call at the time, so we should send it now // to make sure the sad tab shows up, etc. if (render_view_host_->view()) render_view_host_->view()->Show(); else delegate_->RendererGoneFromRenderManager(render_view_host_); // Make sure the size is up to date. (Fix for bug 1079768.) delegate_->UpdateRenderViewSizeForRenderManager(); if (focus_render_view && render_view_host_->view()) render_view_host_->view()->Focus(); RenderViewHostSwitchedDetails details; details.new_host = render_view_host_; details.old_host = old_render_view_host; NotificationService::current()->Notify( NOTIFY_RENDER_VIEW_HOST_CHANGED, Source(delegate_->GetControllerForRenderManager()), Details(&details)); if (destroy_after) old_render_view_host->Shutdown(); // Let the task manager know that we've swapped RenderViewHosts, since it // might need to update its process groupings. delegate_->NotifySwappedFromRenderManager(); } RenderViewHost* RenderViewHostManager::UpdateRendererStateNavigate( const NavigationEntry& entry) { // If we are in PENDING or ENTERING_INTERSTITIAL, then we want to get back // to NORMAL and navigate as usual. if (renderer_state_ == PENDING || renderer_state_ == ENTERING_INTERSTITIAL) { if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); if (interstitial_render_view_host_) CancelRenderView(&interstitial_render_view_host_); renderer_state_ = NORMAL; } // render_view_host_ will not be deleted before the end of this method, so we // don't have to worry about this SiteInstance's ref count dropping to zero. SiteInstance* curr_instance = render_view_host_->site_instance(); if (showing_interstitial_page()) { // Must disable any ability to proceed from the interstitial, because we're // about to navigate somewhere else. DisableInterstitialProceed(true); if (pending_render_view_host_) CancelRenderView(&pending_render_view_host_); renderer_state_ = LEAVING_INTERSTITIAL; // We want to compare against where we were, because we just cancelled // where we were going. The original_render_view_host_ won't be deleted // before the end of this method, so we don't have to worry about this // SiteInstance's ref count dropping to zero. curr_instance = original_render_view_host_->site_instance(); } // Determine if we need a new SiteInstance for this entry. // Again, new_instance won't be deleted before the end of this method, so it // is safe to use a normal pointer here. SiteInstance* new_instance = curr_instance; if (ShouldTransitionCrossSite()) new_instance = GetSiteInstanceForEntry(entry, curr_instance); if (new_instance != curr_instance) { // New SiteInstance. DCHECK(renderer_state_ == NORMAL || renderer_state_ == LEAVING_INTERSTITIAL); // Create a pending RVH and navigate it. bool success = CreatePendingRenderView(new_instance); if (!success) return NULL; // Check if our current RVH is live before we set up a transition. if (!render_view_host_->IsRenderViewLive()) { if (renderer_state_ == NORMAL) { // The current RVH is not live. There's no reason to sit around with a // sad tab or a newly created RVH while we wait for the pending RVH to // navigate. Just switch to the pending RVH now and go back to NORMAL, // without requiring a cross-site transition. (Note that we don't care // about on{before}unload handlers if the current RVH isn't live.) SwapToRenderView(&pending_render_view_host_, true); return render_view_host_; } else if (renderer_state_ == LEAVING_INTERSTITIAL) { // Cancel the interstitial, since it has died and we're navigating away // anyway. DCHECK(original_render_view_host_); if (original_render_view_host_->IsRenderViewLive()) { // Swap back to the original and act like a pending request (using // the logic below). SwapToRenderView(&original_render_view_host_, true); renderer_state_ = NORMAL; InterstitialPageGone(); // Continue with the pending cross-site transition logic below. } else { // Both the interstitial and original are dead. Just like the NORMAL // case, let's skip the cross-site transition entirely. We also have // to clean up the interstitial state. SwapToRenderView(&pending_render_view_host_, true); CancelRenderView(&original_render_view_host_); renderer_state_ = NORMAL; InterstitialPageGone(); return render_view_host_; } } else { NOTREACHED(); return render_view_host_; } } // Otherwise, it's safe to treat this as a pending cross-site transition. // Make sure the old render view stops, in case a load is in progress. render_view_host_->Stop(); // Suspend the new render view (i.e., don't let it send the cross-site // Navigate message) until we hear back from the old renderer's // onbeforeunload handler. If it returns false, we'll have to cancel the // request. pending_render_view_host_->SetNavigationsSuspended(true); // Tell the CrossSiteRequestManager that this RVH has a pending cross-site // request, so that ResourceDispatcherHost will know to tell us to run the // old page's onunload handler before it sends the response. pending_render_view_host_->SetHasPendingCrossSiteRequest(true); // We now have a pending RVH. If we were in NORMAL, we should now be in // PENDING. If we were in LEAVING_INTERSTITIAL, we should stay there. if (renderer_state_ == NORMAL) renderer_state_ = PENDING; else DCHECK(renderer_state_ == LEAVING_INTERSTITIAL); // Tell the old render view to run its onbeforeunload handler, since it // doesn't otherwise know that the cross-site request is happening. This // will trigger a call to ShouldClosePage with the reply. render_view_host_->FirePageBeforeUnload(); return pending_render_view_host_; } // Same SiteInstance can be used. Navigate render_view_host_ if we are in // the NORMAL state, and original_render_view_host_ if an interstitial is // showing. if (renderer_state_ == NORMAL) return render_view_host_; DCHECK(renderer_state_ == LEAVING_INTERSTITIAL); return original_render_view_host_; } void RenderViewHostManager::DisableInterstitialProceed(bool stop_request) { // TODO(creis): Make sure the interstitial page disables any ability to // proceed at this point, because we're about to abort the original request. // This can be done by adding a new event to the NotificationService. // We should also disable the button on the page itself, but it's ok if that // doesn't happen immediately. // Stopping the request is necessary if we are navigating away, because the // user could be requesting the same URL again, causing the HttpCache to // ignore it. (Fixes bug 1079784.) if (stop_request) { original_render_view_host_->Stop(); if (pending_render_view_host_) pending_render_view_host_->Stop(); } } void RenderViewHostManager::InterstitialPageGone() { DCHECK(!showing_interstitial_page()); NotificationService::current()->Notify( NOTIFY_INTERSTITIAL_PAGE_CLOSED, Source(delegate_->GetControllerForRenderManager()), NotificationService::NoDetails()); if (interstitial_delegate_) { interstitial_delegate_->InterstitialClosed(); interstitial_delegate_ = NULL; } }