OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "content/browser/tab_contents/render_view_host_manager.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/command_line.h" | |
10 #include "base/logging.h" | |
11 #include "content/browser/debugger/devtools_manager_impl.h" | |
12 #include "content/browser/renderer_host/render_view_host_factory.h" | |
13 #include "content/browser/renderer_host/render_view_host_impl.h" | |
14 #include "content/browser/site_instance_impl.h" | |
15 #include "content/browser/web_contents/navigation_controller_impl.h" | |
16 #include "content/browser/web_contents/navigation_entry_impl.h" | |
17 #include "content/browser/webui/web_ui_impl.h" | |
18 #include "content/common/view_messages.h" | |
19 #include "content/port/browser/render_widget_host_view_port.h" | |
20 #include "content/public/browser/content_browser_client.h" | |
21 #include "content/public/browser/notification_service.h" | |
22 #include "content/public/browser/notification_types.h" | |
23 #include "content/public/browser/render_view_host_delegate.h" | |
24 #include "content/public/browser/web_contents_view.h" | |
25 #include "content/public/browser/web_ui_controller.h" | |
26 #include "content/public/browser/web_ui_controller_factory.h" | |
27 #include "content/public/common/content_switches.h" | |
28 #include "content/public/common/url_constants.h" | |
29 | |
30 using content::NavigationController; | |
31 using content::NavigationEntry; | |
32 using content::NavigationEntryImpl; | |
33 using content::RenderViewHost; | |
34 using content::RenderViewHostImpl; | |
35 using content::RenderWidgetHostView; | |
36 using content::RenderWidgetHostViewPort; | |
37 using content::SiteInstance; | |
38 using content::WebUIControllerFactory; | |
39 | |
40 RenderViewHostManager::RenderViewHostManager( | |
41 content::RenderViewHostDelegate* render_view_delegate, | |
42 Delegate* delegate) | |
43 : delegate_(delegate), | |
44 cross_navigation_pending_(false), | |
45 render_view_delegate_(render_view_delegate), | |
46 render_view_host_(NULL), | |
47 pending_render_view_host_(NULL), | |
48 interstitial_page_(NULL) { | |
49 } | |
50 | |
51 RenderViewHostManager::~RenderViewHostManager() { | |
52 if (pending_render_view_host_) | |
53 CancelPending(); | |
54 | |
55 // We should always have a main RenderViewHost. | |
56 RenderViewHostImpl* render_view_host = render_view_host_; | |
57 render_view_host_ = NULL; | |
58 render_view_host->Shutdown(); | |
59 | |
60 // Shut down any swapped out RenderViewHosts. | |
61 for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin(); | |
62 iter != swapped_out_hosts_.end(); | |
63 ++iter) { | |
64 iter->second->Shutdown(); | |
65 } | |
66 } | |
67 | |
68 void RenderViewHostManager::Init(content::BrowserContext* browser_context, | |
69 SiteInstance* site_instance, | |
70 int routing_id) { | |
71 // Create a RenderViewHost, once we have an instance. It is important to | |
72 // immediately give this SiteInstance to a RenderViewHost so that it is | |
73 // ref counted. | |
74 if (!site_instance) | |
75 site_instance = SiteInstance::Create(browser_context); | |
76 render_view_host_ = static_cast<RenderViewHostImpl*>( | |
77 RenderViewHostFactory::Create( | |
78 site_instance, render_view_delegate_, routing_id, delegate_-> | |
79 GetControllerForRenderManager().GetSessionStorageNamespace())); | |
80 | |
81 // Keep track of renderer processes as they start to shut down. | |
82 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CLOSING, | |
83 content::NotificationService::AllSources()); | |
84 } | |
85 | |
86 RenderViewHostImpl* RenderViewHostManager::current_host() const { | |
87 return render_view_host_; | |
88 } | |
89 | |
90 RenderViewHostImpl* RenderViewHostManager::pending_render_view_host() const { | |
91 return pending_render_view_host_; | |
92 } | |
93 | |
94 RenderWidgetHostView* RenderViewHostManager::GetRenderWidgetHostView() const { | |
95 if (!render_view_host_) | |
96 return NULL; | |
97 return render_view_host_->GetView(); | |
98 } | |
99 | |
100 RenderViewHostImpl* RenderViewHostManager::Navigate( | |
101 const NavigationEntryImpl& entry) { | |
102 // Create a pending RenderViewHost. It will give us the one we should use | |
103 RenderViewHostImpl* dest_render_view_host = | |
104 static_cast<RenderViewHostImpl*>(UpdateRendererStateForNavigate(entry)); | |
105 if (!dest_render_view_host) | |
106 return NULL; // We weren't able to create a pending render view host. | |
107 | |
108 // If the current render_view_host_ isn't live, we should create it so | |
109 // that we don't show a sad tab while the dest_render_view_host fetches | |
110 // its first page. (Bug 1145340) | |
111 if (dest_render_view_host != render_view_host_ && | |
112 !render_view_host_->IsRenderViewLive()) { | |
113 // Note: we don't call InitRenderView here because we are navigating away | |
114 // soon anyway, and we don't have the NavigationEntry for this host. | |
115 delegate_->CreateRenderViewForRenderManager(render_view_host_); | |
116 } | |
117 | |
118 // If the renderer crashed, then try to create a new one to satisfy this | |
119 // navigation request. | |
120 if (!dest_render_view_host->IsRenderViewLive()) { | |
121 if (!InitRenderView(dest_render_view_host, entry)) | |
122 return NULL; | |
123 | |
124 // Now that we've created a new renderer, be sure to hide it if it isn't | |
125 // our primary one. Otherwise, we might crash if we try to call Show() | |
126 // on it later. | |
127 if (dest_render_view_host != render_view_host_ && | |
128 dest_render_view_host->GetView()) { | |
129 dest_render_view_host->GetView()->Hide(); | |
130 } else { | |
131 // This is our primary renderer, notify here as we won't be calling | |
132 // CommitPending (which does the notify). | |
133 RenderViewHost* null_rvh = NULL; | |
134 std::pair<RenderViewHost*, RenderViewHost*> details = | |
135 std::make_pair(null_rvh, render_view_host_); | |
136 content::NotificationService::current()->Notify( | |
137 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, | |
138 content::Source<NavigationController>( | |
139 &delegate_->GetControllerForRenderManager()), | |
140 content::Details<std::pair<RenderViewHost*, RenderViewHost*> >( | |
141 &details)); | |
142 } | |
143 } | |
144 | |
145 return dest_render_view_host; | |
146 } | |
147 | |
148 void RenderViewHostManager::Stop() { | |
149 render_view_host_->Stop(); | |
150 | |
151 // If we are cross-navigating, we should stop the pending renderers. This | |
152 // will lead to a DidFailProvisionalLoad, which will properly destroy them. | |
153 if (cross_navigation_pending_) { | |
154 pending_render_view_host_->Send( | |
155 new ViewMsg_Stop(pending_render_view_host_->GetRoutingID())); | |
156 } | |
157 } | |
158 | |
159 void RenderViewHostManager::SetIsLoading(bool is_loading) { | |
160 render_view_host_->SetIsLoading(is_loading); | |
161 if (pending_render_view_host_) | |
162 pending_render_view_host_->SetIsLoading(is_loading); | |
163 } | |
164 | |
165 bool RenderViewHostManager::ShouldCloseTabOnUnresponsiveRenderer() { | |
166 if (!cross_navigation_pending_) | |
167 return true; | |
168 | |
169 // If the tab becomes unresponsive during unload while doing a | |
170 // cross-site navigation, proceed with the navigation. (This assumes that | |
171 // the pending RenderViewHost is still responsive.) | |
172 int pending_request_id = pending_render_view_host_->GetPendingRequestId(); | |
173 if (pending_request_id == -1) { | |
174 // Haven't gotten around to starting the request, because we're still | |
175 // waiting for the beforeunload handler to finish. We'll pretend that it | |
176 // did finish, to let the navigation proceed. Note that there's a danger | |
177 // that the beforeunload handler will later finish and possibly return | |
178 // false (meaning the navigation should not proceed), but we'll ignore it | |
179 // in this case because it took too long. | |
180 if (pending_render_view_host_->are_navigations_suspended()) | |
181 pending_render_view_host_->SetNavigationsSuspended(false); | |
182 } else { | |
183 // The request has been started and paused while we're waiting for the | |
184 // unload handler to finish. We'll pretend that it did, by notifying the | |
185 // IO thread to let the response continue. The pending renderer will then | |
186 // be swapped in as part of the usual DidNavigate logic. (If the unload | |
187 // handler later finishes, this call will be ignored because the state in | |
188 // CrossSiteResourceHandler will already be cleaned up.) | |
189 ViewMsg_SwapOut_Params params; | |
190 params.new_render_process_host_id = | |
191 pending_render_view_host_->GetProcess()->GetID(); | |
192 params.new_request_id = pending_request_id; | |
193 current_host()->GetProcess()->CrossSiteSwapOutACK(params); | |
194 } | |
195 return false; | |
196 } | |
197 | |
198 void RenderViewHostManager::DidNavigateMainFrame( | |
199 RenderViewHost* render_view_host) { | |
200 if (!cross_navigation_pending_) { | |
201 DCHECK(!pending_render_view_host_); | |
202 | |
203 // We should only hear this from our current renderer. | |
204 DCHECK(render_view_host == render_view_host_); | |
205 | |
206 // Even when there is no pending RVH, there may be a pending Web UI. | |
207 if (pending_web_ui_.get()) | |
208 CommitPending(); | |
209 return; | |
210 } | |
211 | |
212 if (render_view_host == pending_render_view_host_) { | |
213 // The pending cross-site navigation completed, so show the renderer. | |
214 // If it committed without sending network requests (e.g., data URLs), | |
215 // then we still need to swap out the old RVH first and run its unload | |
216 // handler. OK for that to happen in the background. | |
217 if (pending_render_view_host_->GetPendingRequestId() == -1) { | |
218 OnCrossSiteResponse(pending_render_view_host_->GetProcess()->GetID(), | |
219 pending_render_view_host_->GetRoutingID()); | |
220 } | |
221 | |
222 CommitPending(); | |
223 cross_navigation_pending_ = false; | |
224 } else if (render_view_host == render_view_host_) { | |
225 // A navigation in the original page has taken place. Cancel the pending | |
226 // one. | |
227 CancelPending(); | |
228 cross_navigation_pending_ = false; | |
229 } else { | |
230 // No one else should be sending us DidNavigate in this state. | |
231 DCHECK(false); | |
232 } | |
233 } | |
234 | |
235 void RenderViewHostManager::SetWebUIPostCommit(WebUIImpl* web_ui) { | |
236 DCHECK(!web_ui_.get()); | |
237 web_ui_.reset(web_ui); | |
238 } | |
239 | |
240 void RenderViewHostManager::RendererAbortedProvisionalLoad( | |
241 RenderViewHost* render_view_host) { | |
242 // We used to cancel the pending renderer here for cross-site downloads. | |
243 // However, it's not safe to do that because the download logic repeatedly | |
244 // looks for this TabContents based on a render view ID. Instead, we just | |
245 // leave the pending renderer around until the next navigation event | |
246 // (Navigate, DidNavigate, etc), which will clean it up properly. | |
247 // TODO(creis): All of this will go away when we move the cross-site logic | |
248 // to ResourceDispatcherHost, so that we intercept responses rather than | |
249 // navigation events. (That's necessary to support onunload anyway.) Once | |
250 // we've made that change, we won't create a pending renderer until we know | |
251 // the response is not a download. | |
252 } | |
253 | |
254 void RenderViewHostManager::RendererProcessClosing( | |
255 content::RenderProcessHost* render_process_host) { | |
256 // Remove any swapped out RVHs from this process, so that we don't try to | |
257 // swap them back in while the process is exiting. Start by finding them, | |
258 // since there could be more than one. | |
259 std::list<int> ids_to_remove; | |
260 for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin(); | |
261 iter != swapped_out_hosts_.end(); | |
262 ++iter) { | |
263 if (iter->second->GetProcess() == render_process_host) | |
264 ids_to_remove.push_back(iter->first); | |
265 } | |
266 | |
267 // Now delete them. | |
268 while (!ids_to_remove.empty()) { | |
269 swapped_out_hosts_[ids_to_remove.back()]->Shutdown(); | |
270 swapped_out_hosts_.erase(ids_to_remove.back()); | |
271 ids_to_remove.pop_back(); | |
272 } | |
273 } | |
274 | |
275 void RenderViewHostManager::ShouldClosePage( | |
276 bool for_cross_site_transition, | |
277 bool proceed, | |
278 const base::TimeTicks& proceed_time) { | |
279 if (for_cross_site_transition) { | |
280 // Ignore if we're not in a cross-site navigation. | |
281 if (!cross_navigation_pending_) | |
282 return; | |
283 | |
284 if (proceed) { | |
285 // Ok to unload the current page, so proceed with the cross-site | |
286 // navigation. Note that if navigations are not currently suspended, it | |
287 // might be because the renderer was deemed unresponsive and this call was | |
288 // already made by ShouldCloseTabOnUnresponsiveRenderer. In that case, it | |
289 // is ok to do nothing here. | |
290 if (pending_render_view_host_ && | |
291 pending_render_view_host_->are_navigations_suspended()) { | |
292 pending_render_view_host_->SetNavigationsSuspended(false); | |
293 if (!proceed_time.is_null()) { | |
294 pending_render_view_host_->SetNavigationStartTime(proceed_time); | |
295 } | |
296 } | |
297 } else { | |
298 // Current page says to cancel. | |
299 CancelPending(); | |
300 cross_navigation_pending_ = false; | |
301 } | |
302 } else { | |
303 // Non-cross site transition means closing the entire tab. | |
304 bool proceed_to_fire_unload; | |
305 delegate_->BeforeUnloadFiredFromRenderManager(proceed, | |
306 &proceed_to_fire_unload); | |
307 | |
308 if (proceed_to_fire_unload) { | |
309 // This is not a cross-site navigation, the tab is being closed. | |
310 render_view_host_->ClosePage(); | |
311 } | |
312 } | |
313 } | |
314 | |
315 void RenderViewHostManager::OnCrossSiteResponse(int new_render_process_host_id, | |
316 int new_request_id) { | |
317 // Should only see this while we have a pending renderer. | |
318 if (!cross_navigation_pending_) | |
319 return; | |
320 DCHECK(pending_render_view_host_); | |
321 | |
322 // Tell the old renderer it is being swapped out. This will fire the unload | |
323 // handler (without firing the beforeunload handler a second time). When the | |
324 // unload handler finishes and the navigation completes, we will send a | |
325 // message to the ResourceDispatcherHost with the given pending request IDs, | |
326 // allowing the pending RVH's response to resume. | |
327 render_view_host_->SwapOut(new_render_process_host_id, new_request_id); | |
328 | |
329 // ResourceDispatcherHost has told us to run the onunload handler, which | |
330 // means it is not a download or unsafe page, and we are going to perform the | |
331 // navigation. Thus, we no longer need to remember that the RenderViewHost | |
332 // is part of a pending cross-site request. | |
333 pending_render_view_host_->SetHasPendingCrossSiteRequest(false, | |
334 new_request_id); | |
335 } | |
336 | |
337 void RenderViewHostManager::Observe( | |
338 int type, | |
339 const content::NotificationSource& source, | |
340 const content::NotificationDetails& details) { | |
341 switch (type) { | |
342 case content::NOTIFICATION_RENDERER_PROCESS_CLOSING: | |
343 RendererProcessClosing( | |
344 content::Source<content::RenderProcessHost>(source).ptr()); | |
345 break; | |
346 | |
347 default: | |
348 NOTREACHED(); | |
349 } | |
350 } | |
351 | |
352 bool RenderViewHostManager::ShouldTransitionCrossSite() { | |
353 // True if we are using process-per-site-instance (default) or | |
354 // process-per-site (kProcessPerSite). | |
355 return !CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerTab); | |
356 } | |
357 | |
358 bool RenderViewHostManager::ShouldSwapProcessesForNavigation( | |
359 const NavigationEntry* cur_entry, | |
360 const NavigationEntryImpl* new_entry) const { | |
361 DCHECK(new_entry); | |
362 | |
363 // Check for reasons to swap processes even if we are in a process model that | |
364 // doesn't usually swap (e.g., process-per-tab). | |
365 | |
366 // For security, we should transition between processes when one is a Web UI | |
367 // page and one isn't. If there's no cur_entry, check the current RVH's | |
368 // site, which might already be committed to a Web UI URL (such as the NTP). | |
369 const GURL& current_url = (cur_entry) ? cur_entry->GetURL() : | |
370 render_view_host_->GetSiteInstance()->GetSite(); | |
371 content::BrowserContext* browser_context = | |
372 delegate_->GetControllerForRenderManager().GetBrowserContext(); | |
373 const WebUIControllerFactory* web_ui_factory = | |
374 content::GetContentClient()->browser()->GetWebUIControllerFactory(); | |
375 if (web_ui_factory) { | |
376 if (web_ui_factory->UseWebUIForURL(browser_context, current_url)) { | |
377 // Force swap if it's not an acceptable URL for Web UI. | |
378 if (!web_ui_factory->IsURLAcceptableForWebUI(browser_context, | |
379 new_entry->GetURL())) | |
380 return true; | |
381 } else { | |
382 // Force swap if it's a Web UI URL. | |
383 if (web_ui_factory->UseWebUIForURL(browser_context, new_entry->GetURL())) | |
384 return true; | |
385 } | |
386 } | |
387 | |
388 if (content::GetContentClient()->browser()->ShouldSwapProcessesForNavigation( | |
389 cur_entry ? cur_entry->GetURL() : GURL(), new_entry->GetURL())) { | |
390 return true; | |
391 } | |
392 | |
393 if (!cur_entry) | |
394 return false; | |
395 | |
396 // We can't switch a RenderView between view source and non-view source mode | |
397 // without screwing up the session history sometimes (when navigating between | |
398 // "view-source:http://foo.com/" and "http://foo.com/", WebKit doesn't treat | |
399 // it as a new navigation). So require a view switch. | |
400 if (cur_entry->IsViewSourceMode() != new_entry->IsViewSourceMode()) | |
401 return true; | |
402 | |
403 return false; | |
404 } | |
405 | |
406 SiteInstance* RenderViewHostManager::GetSiteInstanceForEntry( | |
407 const NavigationEntryImpl& entry, | |
408 SiteInstance* curr_instance) { | |
409 // NOTE: This is only called when ShouldTransitionCrossSite is true. | |
410 | |
411 const GURL& dest_url = entry.GetURL(); | |
412 NavigationControllerImpl& controller = | |
413 delegate_->GetControllerForRenderManager(); | |
414 content::BrowserContext* browser_context = controller.GetBrowserContext(); | |
415 | |
416 // If the entry has an instance already we should use it. | |
417 if (entry.site_instance()) | |
418 return entry.site_instance(); | |
419 | |
420 // (UGLY) HEURISTIC, process-per-site only: | |
421 // | |
422 // If this navigation is generated, then it probably corresponds to a search | |
423 // query. Given that search results typically lead to users navigating to | |
424 // other sites, we don't really want to use the search engine hostname to | |
425 // determine the site instance for this navigation. | |
426 // | |
427 // NOTE: This can be removed once we have a way to transition between | |
428 // RenderViews in response to a link click. | |
429 // | |
430 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kProcessPerSite) && | |
431 entry.GetTransitionType() == content::PAGE_TRANSITION_GENERATED) | |
432 return curr_instance; | |
433 | |
434 SiteInstanceImpl* curr_site_instance = | |
435 static_cast<SiteInstanceImpl*>(curr_instance); | |
436 | |
437 // If we haven't used our SiteInstance (and thus RVH) yet, then we can use it | |
438 // for this entry. We won't commit the SiteInstance to this site until the | |
439 // navigation commits (in DidNavigate), unless the navigation entry was | |
440 // restored or it's a Web UI as described below. | |
441 if (!curr_site_instance->HasSite()) { | |
442 // If we've already created a SiteInstance for our destination, we don't | |
443 // want to use this unused SiteInstance; use the existing one. (We don't | |
444 // do this check if the curr_instance has a site, because for now, we want | |
445 // to compare against the current URL and not the SiteInstance's site. In | |
446 // this case, there is no current URL, so comparing against the site is ok. | |
447 // See additional comments below.) | |
448 if (curr_site_instance->HasRelatedSiteInstance(dest_url)) | |
449 return curr_site_instance->GetRelatedSiteInstance(dest_url); | |
450 | |
451 // For extensions, Web UI URLs (such as the new tab page), and apps we do | |
452 // not want to use the curr_instance if it has no site, since it will have a | |
453 // RenderProcessHost of PRIV_NORMAL. Create a new SiteInstance for this | |
454 // URL instead (with the correct process type). | |
455 if (curr_site_instance->HasWrongProcessForURL(dest_url)) | |
456 return curr_site_instance->GetRelatedSiteInstance(dest_url); | |
457 | |
458 // Normally the "site" on the SiteInstance is set lazily when the load | |
459 // actually commits. This is to support better process sharing in case | |
460 // the site redirects to some other site: we want to use the destination | |
461 // site in the site instance. | |
462 // | |
463 // In the case of session restore, as it loads all the pages immediately | |
464 // we need to set the site first, otherwise after a restore none of the | |
465 // pages would share renderers in process-per-site. | |
466 if (entry.restore_type() != NavigationEntryImpl::RESTORE_NONE) | |
467 curr_site_instance->SetSite(dest_url); | |
468 | |
469 return curr_site_instance; | |
470 } | |
471 | |
472 // Otherwise, only create a new SiteInstance for cross-site navigation. | |
473 | |
474 // TODO(creis): Once we intercept links and script-based navigations, we | |
475 // will be able to enforce that all entries in a SiteInstance actually have | |
476 // the same site, and it will be safe to compare the URL against the | |
477 // SiteInstance's site, as follows: | |
478 // const GURL& current_url = curr_instance->site(); | |
479 // For now, though, we're in a hybrid model where you only switch | |
480 // SiteInstances if you type in a cross-site URL. This means we have to | |
481 // compare the entry's URL to the last committed entry's URL. | |
482 NavigationEntry* curr_entry = controller.GetLastCommittedEntry(); | |
483 if (interstitial_page_) { | |
484 // The interstitial is currently the last committed entry, but we want to | |
485 // compare against the last non-interstitial entry. | |
486 curr_entry = controller.GetEntryAtOffset(-1); | |
487 } | |
488 // If there is no last non-interstitial entry (and curr_instance already | |
489 // has a site), then we must have been opened from another tab. We want | |
490 // to compare against the URL of the page that opened us, but we can't | |
491 // get to it directly. The best we can do is check against the site of | |
492 // the SiteInstance. This will be correct when we intercept links and | |
493 // script-based navigations, but for now, it could place some pages in a | |
494 // new process unnecessarily. We should only hit this case if a page tries | |
495 // to open a new tab to an interstitial-inducing URL, and then navigates | |
496 // the page to a different same-site URL. (This seems very unlikely in | |
497 // practice.) | |
498 const GURL& current_url = (curr_entry) ? curr_entry->GetURL() : | |
499 curr_instance->GetSite(); | |
500 | |
501 // Use the current SiteInstance for same site navigations, as long as the | |
502 // process type is correct. (The URL may have been installed as an app since | |
503 // the last time we visited it.) | |
504 if (SiteInstance::IsSameWebSite(browser_context, current_url, dest_url) && | |
505 !static_cast<SiteInstanceImpl*>(curr_instance)->HasWrongProcessForURL( | |
506 dest_url)) { | |
507 return curr_instance; | |
508 } else if (ShouldSwapProcessesForNavigation(curr_entry, &entry)) { | |
509 // When we're swapping, we need to force the site instance AND browsing | |
510 // instance to be different ones. This addresses special cases where we use | |
511 // a single BrowsingInstance for all pages of a certain type (e.g., New Tab | |
512 // Pages), keeping them in the same process. When you navigate away from | |
513 // that page, we want to explicity ignore that BrowsingInstance and group | |
514 // this page into the appropriate SiteInstance for its URL. | |
515 return SiteInstance::CreateForURL(browser_context, dest_url); | |
516 } else { | |
517 // Start the new renderer in a new SiteInstance, but in the current | |
518 // BrowsingInstance. It is important to immediately give this new | |
519 // SiteInstance to a RenderViewHost (if it is different than our current | |
520 // SiteInstance), so that it is ref counted. This will happen in | |
521 // CreatePendingRenderView. | |
522 return curr_instance->GetRelatedSiteInstance(dest_url); | |
523 } | |
524 } | |
525 | |
526 bool RenderViewHostManager::CreatePendingRenderView( | |
527 const NavigationEntryImpl& entry, SiteInstance* instance) { | |
528 NavigationEntry* curr_entry = | |
529 delegate_->GetControllerForRenderManager().GetLastCommittedEntry(); | |
530 if (curr_entry) { | |
531 DCHECK(!curr_entry->GetContentState().empty()); | |
532 // TODO(creis): Should send a message to the RenderView to let it know | |
533 // we're about to switch away, so that it sends an UpdateState message. | |
534 } | |
535 | |
536 // Check if we've already created an RVH for this SiteInstance. | |
537 CHECK(instance); | |
538 RenderViewHostMap::iterator iter = | |
539 swapped_out_hosts_.find(instance->GetId()); | |
540 if (iter != swapped_out_hosts_.end()) { | |
541 // Re-use the existing RenderViewHost, which has already been initialized. | |
542 // We'll remove it from the list of swapped out hosts if it commits. | |
543 pending_render_view_host_ = iter->second; | |
544 | |
545 // Prevent the process from exiting while we're trying to use it. | |
546 pending_render_view_host_->GetProcess()->AddPendingView(); | |
547 | |
548 return true; | |
549 } | |
550 | |
551 pending_render_view_host_ = static_cast<RenderViewHostImpl*>( | |
552 RenderViewHostFactory::Create( | |
553 instance, render_view_delegate_, MSG_ROUTING_NONE, delegate_-> | |
554 GetControllerForRenderManager().GetSessionStorageNamespace())); | |
555 | |
556 // Prevent the process from exiting while we're trying to use it. | |
557 pending_render_view_host_->GetProcess()->AddPendingView(); | |
558 | |
559 bool success = InitRenderView(pending_render_view_host_, entry); | |
560 if (success) { | |
561 // Don't show the view until we get a DidNavigate from it. | |
562 pending_render_view_host_->GetView()->Hide(); | |
563 } else { | |
564 CancelPending(); | |
565 } | |
566 return success; | |
567 } | |
568 | |
569 bool RenderViewHostManager::InitRenderView(RenderViewHost* render_view_host, | |
570 const NavigationEntryImpl& entry) { | |
571 // If the pending navigation is to a WebUI, tell the RenderView about any | |
572 // bindings it will need enabled. | |
573 if (pending_web_ui_.get()) | |
574 render_view_host->AllowBindings(pending_web_ui_->GetBindings()); | |
575 | |
576 return delegate_->CreateRenderViewForRenderManager(render_view_host); | |
577 } | |
578 | |
579 void RenderViewHostManager::CommitPending() { | |
580 // First check whether we're going to want to focus the location bar after | |
581 // this commit. We do this now because the navigation hasn't formally | |
582 // committed yet, so if we've already cleared |pending_web_ui_| the call chain | |
583 // this triggers won't be able to figure out what's going on. | |
584 bool will_focus_location_bar = delegate_->FocusLocationBarByDefault(); | |
585 | |
586 // Next commit the Web UI, if any. | |
587 web_ui_.swap(pending_web_ui_); | |
588 if (web_ui_.get() && pending_web_ui_.get() && !pending_render_view_host_) | |
589 web_ui_->GetController()->DidBecomeActiveForReusedRenderView(); | |
590 pending_web_ui_.reset(); | |
591 | |
592 // It's possible for the pending_render_view_host_ to be NULL when we aren't | |
593 // crossing process boundaries. If so, we just needed to handle the Web UI | |
594 // committing above and we're done. | |
595 if (!pending_render_view_host_) { | |
596 if (will_focus_location_bar) | |
597 delegate_->SetFocusToLocationBar(false); | |
598 return; | |
599 } | |
600 | |
601 // Remember if the page was focused so we can focus the new renderer in | |
602 // that case. | |
603 bool focus_render_view = !will_focus_location_bar && | |
604 render_view_host_->GetView() && render_view_host_->GetView()->HasFocus(); | |
605 | |
606 // Swap in the pending view and make it active. | |
607 RenderViewHostImpl* old_render_view_host = render_view_host_; | |
608 render_view_host_ = pending_render_view_host_; | |
609 pending_render_view_host_ = NULL; | |
610 | |
611 // The process will no longer try to exit, so we can decrement the count. | |
612 render_view_host_->GetProcess()->RemovePendingView(); | |
613 | |
614 // If the view is gone, then this RenderViewHost died while it was hidden. | |
615 // We ignored the RenderViewGone call at the time, so we should send it now | |
616 // to make sure the sad tab shows up, etc. | |
617 if (render_view_host_->GetView()) | |
618 render_view_host_->GetView()->Show(); | |
619 else | |
620 delegate_->RenderViewGoneFromRenderManager(render_view_host_); | |
621 | |
622 // Hide the old view now that the new one is visible. | |
623 if (old_render_view_host->GetView()) { | |
624 old_render_view_host->GetView()->Hide(); | |
625 old_render_view_host->WasSwappedOut(); | |
626 } | |
627 | |
628 // Make sure the size is up to date. (Fix for bug 1079768.) | |
629 delegate_->UpdateRenderViewSizeForRenderManager(); | |
630 | |
631 if (will_focus_location_bar) | |
632 delegate_->SetFocusToLocationBar(false); | |
633 else if (focus_render_view && render_view_host_->GetView()) | |
634 RenderWidgetHostViewPort::FromRWHV(render_view_host_->GetView())->Focus(); | |
635 | |
636 std::pair<RenderViewHost*, RenderViewHost*> details = | |
637 std::make_pair(old_render_view_host, render_view_host_); | |
638 content::NotificationService::current()->Notify( | |
639 content::NOTIFICATION_RENDER_VIEW_HOST_CHANGED, | |
640 content::Source<NavigationController>( | |
641 &delegate_->GetControllerForRenderManager()), | |
642 content::Details<std::pair<RenderViewHost*, RenderViewHost*> >(&details)); | |
643 | |
644 // If the pending view was on the swapped out list, we can remove it. | |
645 swapped_out_hosts_.erase(render_view_host_->GetSiteInstance()->GetId()); | |
646 | |
647 // If the old RVH is live, we are swapping it out and should keep track of it | |
648 // in case we navigate back to it. | |
649 if (old_render_view_host->IsRenderViewLive()) { | |
650 DCHECK(old_render_view_host->is_swapped_out()); | |
651 // Temp fix for http://crbug.com/90867 until we do a better cleanup to make | |
652 // sure we don't get different rvh instances for the same site instance | |
653 // in the same rvhmgr. | |
654 // TODO(creis): Clean this up. | |
655 int32 old_site_instance_id = | |
656 old_render_view_host->GetSiteInstance()->GetId(); | |
657 RenderViewHostMap::iterator iter = | |
658 swapped_out_hosts_.find(old_site_instance_id); | |
659 if (iter != swapped_out_hosts_.end() && | |
660 iter->second != old_render_view_host) { | |
661 // Shutdown the RVH that will be replaced in the map to avoid a leak. | |
662 iter->second->Shutdown(); | |
663 } | |
664 swapped_out_hosts_[old_site_instance_id] = old_render_view_host; | |
665 } else { | |
666 old_render_view_host->Shutdown(); | |
667 } | |
668 | |
669 // Let the task manager know that we've swapped RenderViewHosts, since it | |
670 // might need to update its process groupings. | |
671 delegate_->NotifySwappedFromRenderManager(); | |
672 } | |
673 | |
674 RenderViewHostImpl* RenderViewHostManager::UpdateRendererStateForNavigate( | |
675 const NavigationEntryImpl& entry) { | |
676 // If we are cross-navigating, then we want to get back to normal and navigate | |
677 // as usual. | |
678 if (cross_navigation_pending_) { | |
679 if (pending_render_view_host_) | |
680 CancelPending(); | |
681 cross_navigation_pending_ = false; | |
682 } | |
683 | |
684 // This will possibly create (set to NULL) a Web UI object for the pending | |
685 // page. We'll use this later to give the page special access. This must | |
686 // happen before the new renderer is created below so it will get bindings. | |
687 // It must also happen after the above conditional call to CancelPending(), | |
688 // otherwise CancelPending may clear the pending_web_ui_ and the page will | |
689 // not have it's bindings set appropriately. | |
690 pending_web_ui_.reset(delegate_->CreateWebUIForRenderManager(entry.GetURL())); | |
691 | |
692 // render_view_host_ will not be deleted before the end of this method, so we | |
693 // don't have to worry about this SiteInstance's ref count dropping to zero. | |
694 SiteInstance* curr_instance = render_view_host_->GetSiteInstance(); | |
695 | |
696 // Determine if we need a new SiteInstance for this entry. | |
697 // Again, new_instance won't be deleted before the end of this method, so it | |
698 // is safe to use a normal pointer here. | |
699 SiteInstance* new_instance = curr_instance; | |
700 bool force_swap = ShouldSwapProcessesForNavigation( | |
701 delegate_->GetLastCommittedNavigationEntryForRenderManager(), &entry); | |
702 if (ShouldTransitionCrossSite() || force_swap) | |
703 new_instance = GetSiteInstanceForEntry(entry, curr_instance); | |
704 | |
705 if (new_instance != curr_instance || force_swap) { | |
706 // New SiteInstance. | |
707 DCHECK(!cross_navigation_pending_); | |
708 | |
709 // Create a pending RVH and navigate it. | |
710 bool success = CreatePendingRenderView(entry, new_instance); | |
711 if (!success) | |
712 return NULL; | |
713 | |
714 // Check if our current RVH is live before we set up a transition. | |
715 if (!render_view_host_->IsRenderViewLive()) { | |
716 if (!cross_navigation_pending_) { | |
717 // The current RVH is not live. There's no reason to sit around with a | |
718 // sad tab or a newly created RVH while we wait for the pending RVH to | |
719 // navigate. Just switch to the pending RVH now and go back to non | |
720 // cross-navigating (Note that we don't care about on{before}unload | |
721 // handlers if the current RVH isn't live.) | |
722 CommitPending(); | |
723 return render_view_host_; | |
724 } else { | |
725 NOTREACHED(); | |
726 return render_view_host_; | |
727 } | |
728 } | |
729 // Otherwise, it's safe to treat this as a pending cross-site transition. | |
730 | |
731 // Make sure the old render view stops, in case a load is in progress. | |
732 render_view_host_->Send( | |
733 new ViewMsg_Stop(render_view_host_->GetRoutingID())); | |
734 | |
735 // Suspend the new render view (i.e., don't let it send the cross-site | |
736 // Navigate message) until we hear back from the old renderer's | |
737 // onbeforeunload handler. If the handler returns false, we'll have to | |
738 // cancel the request. | |
739 DCHECK(!pending_render_view_host_->are_navigations_suspended()); | |
740 pending_render_view_host_->SetNavigationsSuspended(true); | |
741 | |
742 // Tell the CrossSiteRequestManager that this RVH has a pending cross-site | |
743 // request, so that ResourceDispatcherHost will know to tell us to run the | |
744 // old page's onunload handler before it sends the response. | |
745 pending_render_view_host_->SetHasPendingCrossSiteRequest(true, -1); | |
746 | |
747 // We now have a pending RVH. | |
748 DCHECK(!cross_navigation_pending_); | |
749 cross_navigation_pending_ = true; | |
750 | |
751 // Tell the old render view to run its onbeforeunload handler, since it | |
752 // doesn't otherwise know that the cross-site request is happening. This | |
753 // will trigger a call to ShouldClosePage with the reply. | |
754 render_view_host_->FirePageBeforeUnload(true); | |
755 | |
756 return pending_render_view_host_; | |
757 } else { | |
758 if (pending_web_ui_.get() && render_view_host_->IsRenderViewLive()) | |
759 pending_web_ui_->GetController()->RenderViewReused(render_view_host_); | |
760 | |
761 // The renderer can exit view source mode when any error or cancellation | |
762 // happen. We must overwrite to recover the mode. | |
763 if (entry.IsViewSourceMode()) { | |
764 render_view_host_->Send( | |
765 new ViewMsg_EnableViewSourceMode(render_view_host_->GetRoutingID())); | |
766 } | |
767 } | |
768 | |
769 // Same SiteInstance can be used. Navigate render_view_host_ if we are not | |
770 // cross navigating. | |
771 DCHECK(!cross_navigation_pending_); | |
772 return render_view_host_; | |
773 } | |
774 | |
775 void RenderViewHostManager::CancelPending() { | |
776 RenderViewHostImpl* pending_render_view_host = pending_render_view_host_; | |
777 pending_render_view_host_ = NULL; | |
778 | |
779 content::DevToolsManagerImpl::GetInstance()->OnCancelPendingNavigation( | |
780 pending_render_view_host, | |
781 render_view_host_); | |
782 | |
783 // We no longer need to prevent the process from exiting. | |
784 pending_render_view_host->GetProcess()->RemovePendingView(); | |
785 | |
786 // The pending RVH may already be on the swapped out list if we started to | |
787 // swap it back in and then canceled. If so, make sure it gets swapped out | |
788 // again. If it's not on the swapped out list (e.g., aborting a pending | |
789 // load), then it's safe to shut down. | |
790 if (IsSwappedOut(pending_render_view_host)) { | |
791 // Any currently suspended navigations are no longer needed. | |
792 pending_render_view_host->CancelSuspendedNavigations(); | |
793 | |
794 // We can pass -1,-1 because there is no pending response in the | |
795 // ResourceDispatcherHost to unpause. | |
796 pending_render_view_host->SwapOut(-1, -1); | |
797 } else { | |
798 // We won't be coming back, so shut this one down. | |
799 pending_render_view_host->Shutdown(); | |
800 } | |
801 | |
802 pending_web_ui_.reset(); | |
803 } | |
804 | |
805 void RenderViewHostManager::RenderViewDeleted(RenderViewHost* rvh) { | |
806 // We are doing this in order to work around and to track a crasher | |
807 // (http://crbug.com/23411) where it seems that pending_render_view_host_ is | |
808 // deleted (not sure from where) but not NULLed. | |
809 if (rvh == pending_render_view_host_) { | |
810 // If you hit this NOTREACHED, please report it in the following bug | |
811 // http://crbug.com/23411 Make sure to include what you were doing when it | |
812 // happened (navigating to a new page, closing a tab...) and if you can | |
813 // reproduce. | |
814 NOTREACHED(); | |
815 pending_render_view_host_ = NULL; | |
816 } | |
817 | |
818 // Make sure deleted RVHs are not kept in the swapped out list while we are | |
819 // still alive. (If render_view_host_ is null, we're already being deleted.) | |
820 if (!render_view_host_) | |
821 return; | |
822 // We can't look it up by SiteInstance ID, which may no longer be valid. | |
823 for (RenderViewHostMap::iterator iter = swapped_out_hosts_.begin(); | |
824 iter != swapped_out_hosts_.end(); | |
825 ++iter) { | |
826 if (iter->second == rvh) { | |
827 swapped_out_hosts_.erase(iter); | |
828 break; | |
829 } | |
830 } | |
831 } | |
832 | |
833 bool RenderViewHostManager::IsSwappedOut(RenderViewHost* rvh) { | |
834 if (!rvh->GetSiteInstance()) | |
835 return false; | |
836 | |
837 return swapped_out_hosts_.find(rvh->GetSiteInstance()->GetId()) != | |
838 swapped_out_hosts_.end(); | |
839 } | |
OLD | NEW |