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.
5 #include "chrome/browser/prerender/prerender_link_manager.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/metrics/field_trial.h"
15 #include "base/metrics/histogram.h"
16 #include "chrome/browser/prerender/prerender_contents.h"
17 #include "chrome/browser/prerender/prerender_handle.h"
18 #include "chrome/browser/prerender/prerender_manager.h"
19 #include "chrome/browser/prerender/prerender_manager_factory.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/prerender_messages.h"
22 #include "chrome/common/prerender_types.h"
23 #include "content/public/browser/render_process_host.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/session_storage_namespace.h"
26 #include "content/public/common/referrer.h"
27 #include "ui/gfx/size.h"
30 #if defined(ENABLE_EXTENSIONS)
31 #include "extensions/browser/guest_view/guest_view_base.h"
34 using base::TimeDelta
;
35 using base::TimeTicks
;
36 using content::RenderViewHost
;
37 using content::SessionStorageNamespace
;
43 bool ShouldStartRelNextPrerenders() {
44 const std::string experiment_name
=
45 base::FieldTrialList::FindFullName("PrerenderRelNextTrial");
47 return experiment_name
.find("Yes") != std::string::npos
;
50 bool ShouldStartPrerender(const uint32 rel_types
) {
51 const bool should_start_rel_next_prerenders
=
52 ShouldStartRelNextPrerenders();
54 if (rel_types
& PrerenderRelTypePrerender
) {
56 } else if (should_start_rel_next_prerenders
&&
57 (rel_types
& PrerenderRelTypeNext
) == PrerenderRelTypeNext
) {
63 COMPILE_ASSERT(PrerenderRelTypePrerender
== 0x1,
64 RelTypeHistogramEnum_must_match_PrerenderRelType
);
65 COMPILE_ASSERT(PrerenderRelTypeNext
== 0x2,
66 RelTypeHistogramEnum_must_match_PrerenderRelType
);
67 enum RelTypeHistogramEnum
{
68 RelTypeHistogramEnumNone
= 0,
69 RelTypeHistogramEnumPrerender
= PrerenderRelTypePrerender
,
70 RelTypeHistogramEnumNext
= PrerenderRelTypeNext
,
71 RelTypeHistogramEnumPrerenderAndNext
=
72 PrerenderRelTypePrerender
| PrerenderRelTypeNext
,
73 RelTypeHistogramEnumMax
,
76 void RecordLinkManagerAdded(const uint32 rel_types
) {
77 const uint32 enum_value
= rel_types
& (RelTypeHistogramEnumMax
- 1);
78 UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkAdded", enum_value
,
79 RelTypeHistogramEnumMax
);
82 void RecordLinkManagerStarting(const uint32 rel_types
) {
83 const uint32 enum_value
= rel_types
& (RelTypeHistogramEnumMax
- 1);
84 UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkStarted", enum_value
,
85 RelTypeHistogramEnumMax
);
88 void Send(int child_id
, IPC::Message
* raw_message
) {
89 using content::RenderProcessHost
;
90 scoped_ptr
<IPC::Message
> own_message(raw_message
);
92 RenderProcessHost
* render_process_host
= RenderProcessHost::FromID(child_id
);
93 if (!render_process_host
)
95 render_process_host
->Send(own_message
.release());
100 // Helper class to implement PrerenderContents::Observer and watch prerenders
101 // which launch other prerenders.
102 class PrerenderLinkManager::PendingPrerenderManager
103 : public PrerenderContents::Observer
{
105 explicit PendingPrerenderManager(PrerenderLinkManager
* link_manager
)
106 : link_manager_(link_manager
) {}
108 virtual ~PendingPrerenderManager() {
109 DCHECK(observed_launchers_
.empty());
110 for (std::set
<PrerenderContents
*>::iterator i
= observed_launchers_
.begin();
111 i
!= observed_launchers_
.end(); ++i
) {
112 (*i
)->RemoveObserver(this);
116 void ObserveLauncher(PrerenderContents
* launcher
) {
117 DCHECK_EQ(FINAL_STATUS_MAX
, launcher
->final_status());
118 if (observed_launchers_
.find(launcher
) != observed_launchers_
.end())
120 observed_launchers_
.insert(launcher
);
121 launcher
->AddObserver(this);
124 virtual void OnPrerenderStart(PrerenderContents
* launcher
) OVERRIDE
{}
126 virtual void OnPrerenderStop(PrerenderContents
* launcher
) OVERRIDE
{
127 observed_launchers_
.erase(launcher
);
128 if (launcher
->final_status() == FINAL_STATUS_USED
) {
129 link_manager_
->StartPendingPrerendersForLauncher(launcher
);
131 link_manager_
->CancelPendingPrerendersForLauncher(launcher
);
136 // A pointer to the parent PrerenderLinkManager.
137 PrerenderLinkManager
* link_manager_
;
139 // The set of PrerenderContentses being observed. Lifetimes are managed by
141 std::set
<PrerenderContents
*> observed_launchers_
;
144 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager
* manager
)
145 : has_shutdown_(false),
147 pending_prerender_manager_(new PendingPrerenderManager(this)) {}
149 PrerenderLinkManager::~PrerenderLinkManager() {
150 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
151 i
!= prerenders_
.end(); ++i
) {
153 DCHECK(!i
->handle
->IsPrerendering())
154 << "All running prerenders should stop at the same time as the "
155 << "PrerenderManager.";
162 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id
,
166 const content::Referrer
& referrer
,
167 const gfx::Size
& size
,
168 int render_view_route_id
) {
169 DCHECK_EQ(static_cast<LinkPrerender
*>(NULL
),
170 FindByLauncherChildIdAndPrerenderId(launcher_child_id
,
173 #if defined(ENABLE_EXTENSIONS)
174 content::RenderViewHost
* rvh
=
175 content::RenderViewHost::FromID(launcher_child_id
, render_view_route_id
);
176 content::WebContents
* web_contents
=
177 rvh
? content::WebContents::FromRenderViewHost(rvh
) : NULL
;
178 // Guests inside <webview> do not support cross-process navigation and so we
179 // do not allow guests to prerender content.
180 if (extensions::GuestViewBase::IsGuest(web_contents
))
184 // Check if the launcher is itself an unswapped prerender.
185 PrerenderContents
* prerender_contents
=
186 manager_
->GetPrerenderContentsForRoute(launcher_child_id
,
187 render_view_route_id
);
188 if (prerender_contents
&&
189 prerender_contents
->final_status() != FINAL_STATUS_MAX
) {
190 // The launcher is a prerender about to be destroyed asynchronously, but
191 // its AddLinkRelPrerender message raced with shutdown. Ignore it.
192 DCHECK_NE(FINAL_STATUS_USED
, prerender_contents
->final_status());
197 prerender(launcher_child_id
, prerender_id
, url
, rel_types
, referrer
, size
,
198 render_view_route_id
, manager_
->GetCurrentTimeTicks(),
200 prerenders_
.push_back(prerender
);
201 RecordLinkManagerAdded(rel_types
);
202 if (prerender_contents
)
203 pending_prerender_manager_
->ObserveLauncher(prerender_contents
);
208 void PrerenderLinkManager::OnCancelPrerender(int child_id
, int prerender_id
) {
209 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
214 CancelPrerender(prerender
);
218 void PrerenderLinkManager::OnAbandonPrerender(int child_id
, int prerender_id
) {
219 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
224 if (!prerender
->handle
) {
225 RemovePrerender(prerender
);
229 prerender
->has_been_abandoned
= true;
230 prerender
->handle
->OnNavigateAway();
231 DCHECK(prerender
->handle
);
233 // If the prerender is not running, remove it from the list so it does not
234 // leak. If it is running, it will send a cancel event when it stops which
236 if (!prerender
->handle
->IsPrerendering())
237 RemovePrerender(prerender
);
240 void PrerenderLinkManager::OnChannelClosing(int child_id
) {
241 std::list
<LinkPrerender
>::iterator next
= prerenders_
.begin();
242 while (next
!= prerenders_
.end()) {
243 std::list
<LinkPrerender
>::iterator it
= next
;
246 if (child_id
!= it
->launcher_child_id
)
249 const size_t running_prerender_count
= CountRunningPrerenders();
250 OnAbandonPrerender(child_id
, it
->prerender_id
);
251 DCHECK_EQ(running_prerender_count
, CountRunningPrerenders());
255 PrerenderLinkManager::LinkPrerender::LinkPrerender(
256 int launcher_child_id
,
260 const content::Referrer
& referrer
,
261 const gfx::Size
& size
,
262 int render_view_route_id
,
263 TimeTicks creation_time
,
264 PrerenderContents
* deferred_launcher
)
265 : launcher_child_id(launcher_child_id
),
266 prerender_id(prerender_id
),
268 rel_types(rel_types
),
271 render_view_route_id(render_view_route_id
),
272 creation_time(creation_time
),
273 deferred_launcher(deferred_launcher
),
275 is_match_complete_replacement(false),
276 has_been_abandoned(false) {
279 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
280 DCHECK_EQ(static_cast<PrerenderHandle
*>(NULL
), handle
)
281 << "The PrerenderHandle should be destroyed before its Prerender.";
284 bool PrerenderLinkManager::IsEmpty() const {
285 return prerenders_
.empty();
288 size_t PrerenderLinkManager::CountRunningPrerenders() const {
290 for (std::list
<LinkPrerender
>::const_iterator i
= prerenders_
.begin();
291 i
!= prerenders_
.end(); ++i
) {
292 if (i
->handle
&& i
->handle
->IsPrerendering())
298 void PrerenderLinkManager::StartPrerenders() {
302 size_t total_started_prerender_count
= 0;
303 std::list
<LinkPrerender
*> abandoned_prerenders
;
304 std::list
<std::list
<LinkPrerender
>::iterator
> pending_prerenders
;
305 std::multiset
<std::pair
<int, int> >
306 running_launcher_and_render_view_routes
;
308 // Scan the list, counting how many prerenders have handles (and so were added
309 // to the PrerenderManager). The count is done for the system as a whole, and
310 // also per launcher.
311 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
312 i
!= prerenders_
.end(); ++i
) {
313 // Skip prerenders launched by a prerender.
314 if (i
->deferred_launcher
)
317 pending_prerenders
.push_back(i
);
319 ++total_started_prerender_count
;
320 if (i
->has_been_abandoned
) {
321 abandoned_prerenders
.push_back(&(*i
));
323 // We do not count abandoned prerenders towards their launcher, since it
324 // has already navigated on to another page.
325 std::pair
<int, int> launcher_and_render_view_route(
326 i
->launcher_child_id
, i
->render_view_route_id
);
327 running_launcher_and_render_view_routes
.insert(
328 launcher_and_render_view_route
);
329 DCHECK_GE(manager_
->config().max_link_concurrency_per_launcher
,
330 running_launcher_and_render_view_routes
.count(
331 launcher_and_render_view_route
));
335 DCHECK_EQ(&(*i
), FindByLauncherChildIdAndPrerenderId(i
->launcher_child_id
,
338 DCHECK_LE(abandoned_prerenders
.size(), total_started_prerender_count
);
339 DCHECK_GE(manager_
->config().max_link_concurrency
,
340 total_started_prerender_count
);
341 DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count
);
343 TimeTicks now
= manager_
->GetCurrentTimeTicks();
345 // Scan the pending prerenders, starting prerenders as we can.
346 for (std::list
<std::list
<LinkPrerender
>::iterator
>::const_iterator
347 i
= pending_prerenders
.begin(), end
= pending_prerenders
.end();
349 TimeDelta prerender_age
= now
- (*i
)->creation_time
;
350 if (prerender_age
>= manager_
->config().max_wait_to_launch
) {
351 // This prerender waited too long in the queue before launching.
352 prerenders_
.erase(*i
);
356 std::pair
<int, int> launcher_and_render_view_route(
357 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
);
358 if (manager_
->config().max_link_concurrency_per_launcher
<=
359 running_launcher_and_render_view_routes
.count(
360 launcher_and_render_view_route
)) {
361 // This prerender's launcher is already at its limit.
365 if (total_started_prerender_count
>=
366 manager_
->config().max_link_concurrency
||
367 total_started_prerender_count
>= prerenders_
.size()) {
368 // The system is already at its prerender concurrency limit. Can we kill
369 // an abandoned prerender to make room?
370 if (!abandoned_prerenders
.empty()) {
371 CancelPrerender(abandoned_prerenders
.front());
372 --total_started_prerender_count
;
373 abandoned_prerenders
.pop_front();
379 if (!ShouldStartPrerender((*i
)->rel_types
)) {
380 prerenders_
.erase(*i
);
384 PrerenderHandle
* handle
= manager_
->AddPrerenderFromLinkRelPrerender(
385 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
,
386 (*i
)->url
, (*i
)->rel_types
, (*i
)->referrer
, (*i
)->size
);
388 // This prerender couldn't be launched, it's gone.
389 prerenders_
.erase(*i
);
393 // We have successfully started a new prerender.
394 (*i
)->handle
= handle
;
395 ++total_started_prerender_count
;
396 handle
->SetObserver(this);
397 if (handle
->IsPrerendering())
398 OnPrerenderStart(handle
);
399 RecordLinkManagerStarting((*i
)->rel_types
);
401 running_launcher_and_render_view_routes
.insert(
402 launcher_and_render_view_route
);
406 PrerenderLinkManager::LinkPrerender
*
407 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id
,
409 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
410 i
!= prerenders_
.end(); ++i
) {
411 if (launcher_child_id
== i
->launcher_child_id
&&
412 prerender_id
== i
->prerender_id
) {
419 PrerenderLinkManager::LinkPrerender
*
420 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle
* prerender_handle
) {
421 DCHECK(prerender_handle
);
422 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
423 i
!= prerenders_
.end(); ++i
) {
424 if (prerender_handle
== i
->handle
)
430 void PrerenderLinkManager::RemovePrerender(LinkPrerender
* prerender
) {
431 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
432 i
!= prerenders_
.end(); ++i
) {
433 if (&(*i
) == prerender
) {
434 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
436 prerenders_
.erase(i
);
443 void PrerenderLinkManager::CancelPrerender(LinkPrerender
* prerender
) {
444 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
445 i
!= prerenders_
.end(); ++i
) {
446 if (&(*i
) == prerender
) {
447 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
449 prerenders_
.erase(i
);
451 own_handle
->OnCancel();
458 void PrerenderLinkManager::StartPendingPrerendersForLauncher(
459 PrerenderContents
* launcher
) {
460 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
461 i
!= prerenders_
.end(); ++i
) {
462 if (i
->deferred_launcher
== launcher
)
463 i
->deferred_launcher
= NULL
;
468 void PrerenderLinkManager::CancelPendingPrerendersForLauncher(
469 PrerenderContents
* launcher
) {
470 // Remove all pending prerenders for this launcher.
471 std::vector
<std::list
<LinkPrerender
>::iterator
> to_erase
;
472 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
473 i
!= prerenders_
.end(); ++i
) {
474 if (i
->deferred_launcher
== launcher
) {
476 to_erase
.push_back(i
);
479 std::for_each(to_erase
.begin(), to_erase
.end(),
480 std::bind1st(std::mem_fun(&std::list
<LinkPrerender
>::erase
),
484 void PrerenderLinkManager::Shutdown() {
485 has_shutdown_
= true;
488 // In practice, this is always called from PrerenderLinkManager::OnAddPrerender.
489 void PrerenderLinkManager::OnPrerenderStart(
490 PrerenderHandle
* prerender_handle
) {
491 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
494 Send(prerender
->launcher_child_id
,
495 new PrerenderMsg_OnPrerenderStart(prerender
->prerender_id
));
498 void PrerenderLinkManager::OnPrerenderStopLoading(
499 PrerenderHandle
* prerender_handle
) {
500 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
504 Send(prerender
->launcher_child_id
,
505 new PrerenderMsg_OnPrerenderStopLoading(prerender
->prerender_id
));
508 void PrerenderLinkManager::OnPrerenderDomContentLoaded(
509 PrerenderHandle
* prerender_handle
) {
510 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
514 Send(prerender
->launcher_child_id
,
515 new PrerenderMsg_OnPrerenderDomContentLoaded(prerender
->prerender_id
));
518 void PrerenderLinkManager::OnPrerenderStop(
519 PrerenderHandle
* prerender_handle
) {
520 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
524 // If the prerender became a match complete replacement, the stop
525 // message has already been sent.
526 if (!prerender
->is_match_complete_replacement
) {
527 Send(prerender
->launcher_child_id
,
528 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
530 RemovePrerender(prerender
);
534 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
535 PrerenderHandle
* prerender_handle
) {
536 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
540 DCHECK(!prerender
->is_match_complete_replacement
);
541 prerender
->is_match_complete_replacement
= true;
542 Send(prerender
->launcher_child_id
,
543 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
544 // Do not call RemovePrerender here. The replacement needs to stay connected
545 // to the HTMLLinkElement in the renderer so it notices renderer-triggered
549 } // namespace prerender