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 using base::TimeDelta
;
31 using base::TimeTicks
;
32 using content::RenderViewHost
;
33 using content::SessionStorageNamespace
;
39 bool ShouldStartRelNextPrerenders() {
40 const std::string experiment_name
=
41 base::FieldTrialList::FindFullName("PrerenderRelNextTrial");
43 return experiment_name
.find("Yes") != std::string::npos
;
46 bool ShouldStartPrerender(const uint32 rel_types
) {
47 const bool should_start_rel_next_prerenders
=
48 ShouldStartRelNextPrerenders();
50 if (rel_types
& PrerenderRelTypePrerender
) {
52 } else if (should_start_rel_next_prerenders
&&
53 (rel_types
& PrerenderRelTypeNext
) == PrerenderRelTypeNext
) {
59 COMPILE_ASSERT(PrerenderRelTypePrerender
== 0x1,
60 RelTypeHistogramEnum_must_match_PrerenderRelType
);
61 COMPILE_ASSERT(PrerenderRelTypeNext
== 0x2,
62 RelTypeHistogramEnum_must_match_PrerenderRelType
);
63 enum RelTypeHistogramEnum
{
64 RelTypeHistogramEnumNone
= 0,
65 RelTypeHistogramEnumPrerender
= PrerenderRelTypePrerender
,
66 RelTypeHistogramEnumNext
= PrerenderRelTypeNext
,
67 RelTypeHistogramEnumPrerenderAndNext
=
68 PrerenderRelTypePrerender
| PrerenderRelTypeNext
,
69 RelTypeHistogramEnumMax
,
72 void RecordLinkManagerAdded(const uint32 rel_types
) {
73 const uint32 enum_value
= rel_types
& (RelTypeHistogramEnumMax
- 1);
74 UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkAdded", enum_value
,
75 RelTypeHistogramEnumMax
);
78 void RecordLinkManagerStarting(const uint32 rel_types
) {
79 const uint32 enum_value
= rel_types
& (RelTypeHistogramEnumMax
- 1);
80 UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkStarted", enum_value
,
81 RelTypeHistogramEnumMax
);
84 void Send(int child_id
, IPC::Message
* raw_message
) {
85 using content::RenderProcessHost
;
86 scoped_ptr
<IPC::Message
> own_message(raw_message
);
88 RenderProcessHost
* render_process_host
= RenderProcessHost::FromID(child_id
);
89 if (!render_process_host
)
91 render_process_host
->Send(own_message
.release());
96 // Helper class to implement PrerenderContents::Observer and watch prerenders
97 // which launch other prerenders.
98 class PrerenderLinkManager::PendingPrerenderManager
99 : public PrerenderContents::Observer
{
101 explicit PendingPrerenderManager(PrerenderLinkManager
* link_manager
)
102 : link_manager_(link_manager
) {}
104 virtual ~PendingPrerenderManager() {
105 DCHECK(observed_launchers_
.empty());
106 for (std::set
<PrerenderContents
*>::iterator i
= observed_launchers_
.begin();
107 i
!= observed_launchers_
.end(); ++i
) {
108 (*i
)->RemoveObserver(this);
112 void ObserveLauncher(PrerenderContents
* launcher
) {
113 DCHECK_EQ(FINAL_STATUS_MAX
, launcher
->final_status());
114 if (observed_launchers_
.find(launcher
) != observed_launchers_
.end())
116 observed_launchers_
.insert(launcher
);
117 launcher
->AddObserver(this);
120 virtual void OnPrerenderStart(PrerenderContents
* launcher
) OVERRIDE
{}
122 virtual void OnPrerenderStop(PrerenderContents
* launcher
) OVERRIDE
{
123 observed_launchers_
.erase(launcher
);
124 if (launcher
->final_status() == FINAL_STATUS_USED
) {
125 link_manager_
->StartPendingPrerendersForLauncher(launcher
);
127 link_manager_
->CancelPendingPrerendersForLauncher(launcher
);
132 // A pointer to the parent PrerenderLinkManager.
133 PrerenderLinkManager
* link_manager_
;
135 // The set of PrerenderContentses being observed. Lifetimes are managed by
137 std::set
<PrerenderContents
*> observed_launchers_
;
140 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager
* manager
)
141 : has_shutdown_(false),
143 pending_prerender_manager_(new PendingPrerenderManager(this)) {}
145 PrerenderLinkManager::~PrerenderLinkManager() {
146 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
147 i
!= prerenders_
.end(); ++i
) {
149 DCHECK(!i
->handle
->IsPrerendering())
150 << "All running prerenders should stop at the same time as the "
151 << "PrerenderManager.";
158 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id
,
162 const content::Referrer
& referrer
,
163 const gfx::Size
& size
,
164 int render_view_route_id
) {
165 DCHECK_EQ(static_cast<LinkPrerender
*>(NULL
),
166 FindByLauncherChildIdAndPrerenderId(launcher_child_id
,
168 content::RenderProcessHost
* rph
=
169 content::RenderProcessHost::FromID(launcher_child_id
);
170 // Guests inside <webview> do not support cross-process navigation and so we
171 // do not allow guests to prerender content.
172 if (rph
&& rph
->IsGuest())
175 // Check if the launcher is itself an unswapped prerender.
176 PrerenderContents
* prerender_contents
=
177 manager_
->GetPrerenderContentsForRoute(launcher_child_id
,
178 render_view_route_id
);
179 if (prerender_contents
&&
180 prerender_contents
->final_status() != FINAL_STATUS_MAX
) {
181 // The launcher is a prerender about to be destroyed asynchronously, but
182 // its AddLinkRelPrerender message raced with shutdown. Ignore it.
183 DCHECK_NE(FINAL_STATUS_USED
, prerender_contents
->final_status());
188 prerender(launcher_child_id
, prerender_id
, url
, rel_types
, referrer
, size
,
189 render_view_route_id
, manager_
->GetCurrentTimeTicks(),
191 prerenders_
.push_back(prerender
);
192 RecordLinkManagerAdded(rel_types
);
193 if (prerender_contents
)
194 pending_prerender_manager_
->ObserveLauncher(prerender_contents
);
199 void PrerenderLinkManager::OnCancelPrerender(int child_id
, int prerender_id
) {
200 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
205 CancelPrerender(prerender
);
209 void PrerenderLinkManager::OnAbandonPrerender(int child_id
, int prerender_id
) {
210 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
215 if (!prerender
->handle
) {
216 RemovePrerender(prerender
);
220 prerender
->has_been_abandoned
= true;
221 prerender
->handle
->OnNavigateAway();
222 DCHECK(prerender
->handle
);
224 // If the prerender is not running, remove it from the list so it does not
225 // leak. If it is running, it will send a cancel event when it stops which
227 if (!prerender
->handle
->IsPrerendering())
228 RemovePrerender(prerender
);
231 void PrerenderLinkManager::OnChannelClosing(int child_id
) {
232 std::list
<LinkPrerender
>::iterator next
= prerenders_
.begin();
233 while (next
!= prerenders_
.end()) {
234 std::list
<LinkPrerender
>::iterator it
= next
;
237 if (child_id
!= it
->launcher_child_id
)
240 const size_t running_prerender_count
= CountRunningPrerenders();
241 OnAbandonPrerender(child_id
, it
->prerender_id
);
242 DCHECK_EQ(running_prerender_count
, CountRunningPrerenders());
246 PrerenderLinkManager::LinkPrerender::LinkPrerender(
247 int launcher_child_id
,
251 const content::Referrer
& referrer
,
252 const gfx::Size
& size
,
253 int render_view_route_id
,
254 TimeTicks creation_time
,
255 PrerenderContents
* deferred_launcher
)
256 : launcher_child_id(launcher_child_id
),
257 prerender_id(prerender_id
),
259 rel_types(rel_types
),
262 render_view_route_id(render_view_route_id
),
263 creation_time(creation_time
),
264 deferred_launcher(deferred_launcher
),
266 is_match_complete_replacement(false),
267 has_been_abandoned(false) {
270 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
271 DCHECK_EQ(static_cast<PrerenderHandle
*>(NULL
), handle
)
272 << "The PrerenderHandle should be destroyed before its Prerender.";
275 bool PrerenderLinkManager::IsEmpty() const {
276 return prerenders_
.empty();
279 size_t PrerenderLinkManager::CountRunningPrerenders() const {
281 for (std::list
<LinkPrerender
>::const_iterator i
= prerenders_
.begin();
282 i
!= prerenders_
.end(); ++i
) {
283 if (i
->handle
&& i
->handle
->IsPrerendering())
289 void PrerenderLinkManager::StartPrerenders() {
293 size_t total_started_prerender_count
= 0;
294 std::list
<LinkPrerender
*> abandoned_prerenders
;
295 std::list
<std::list
<LinkPrerender
>::iterator
> pending_prerenders
;
296 std::multiset
<std::pair
<int, int> >
297 running_launcher_and_render_view_routes
;
299 // Scan the list, counting how many prerenders have handles (and so were added
300 // to the PrerenderManager). The count is done for the system as a whole, and
301 // also per launcher.
302 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
303 i
!= prerenders_
.end(); ++i
) {
304 // Skip prerenders launched by a prerender.
305 if (i
->deferred_launcher
)
308 pending_prerenders
.push_back(i
);
310 ++total_started_prerender_count
;
311 if (i
->has_been_abandoned
) {
312 abandoned_prerenders
.push_back(&(*i
));
314 // We do not count abandoned prerenders towards their launcher, since it
315 // has already navigated on to another page.
316 std::pair
<int, int> launcher_and_render_view_route(
317 i
->launcher_child_id
, i
->render_view_route_id
);
318 running_launcher_and_render_view_routes
.insert(
319 launcher_and_render_view_route
);
320 DCHECK_GE(manager_
->config().max_link_concurrency_per_launcher
,
321 running_launcher_and_render_view_routes
.count(
322 launcher_and_render_view_route
));
326 DCHECK_EQ(&(*i
), FindByLauncherChildIdAndPrerenderId(i
->launcher_child_id
,
329 DCHECK_LE(abandoned_prerenders
.size(), total_started_prerender_count
);
330 DCHECK_GE(manager_
->config().max_link_concurrency
,
331 total_started_prerender_count
);
332 DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count
);
334 TimeTicks now
= manager_
->GetCurrentTimeTicks();
336 // Scan the pending prerenders, starting prerenders as we can.
337 for (std::list
<std::list
<LinkPrerender
>::iterator
>::const_iterator
338 i
= pending_prerenders
.begin(), end
= pending_prerenders
.end();
340 TimeDelta prerender_age
= now
- (*i
)->creation_time
;
341 if (prerender_age
>= manager_
->config().max_wait_to_launch
) {
342 // This prerender waited too long in the queue before launching.
343 prerenders_
.erase(*i
);
347 std::pair
<int, int> launcher_and_render_view_route(
348 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
);
349 if (manager_
->config().max_link_concurrency_per_launcher
<=
350 running_launcher_and_render_view_routes
.count(
351 launcher_and_render_view_route
)) {
352 // This prerender's launcher is already at its limit.
356 if (total_started_prerender_count
>=
357 manager_
->config().max_link_concurrency
||
358 total_started_prerender_count
>= prerenders_
.size()) {
359 // The system is already at its prerender concurrency limit. Can we kill
360 // an abandoned prerender to make room?
361 if (!abandoned_prerenders
.empty()) {
362 CancelPrerender(abandoned_prerenders
.front());
363 --total_started_prerender_count
;
364 abandoned_prerenders
.pop_front();
370 if (!ShouldStartPrerender((*i
)->rel_types
)) {
371 prerenders_
.erase(*i
);
375 PrerenderHandle
* handle
= manager_
->AddPrerenderFromLinkRelPrerender(
376 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
,
377 (*i
)->url
, (*i
)->rel_types
, (*i
)->referrer
, (*i
)->size
);
379 // This prerender couldn't be launched, it's gone.
380 prerenders_
.erase(*i
);
384 // We have successfully started a new prerender.
385 (*i
)->handle
= handle
;
386 ++total_started_prerender_count
;
387 handle
->SetObserver(this);
388 if (handle
->IsPrerendering())
389 OnPrerenderStart(handle
);
390 RecordLinkManagerStarting((*i
)->rel_types
);
392 running_launcher_and_render_view_routes
.insert(
393 launcher_and_render_view_route
);
397 PrerenderLinkManager::LinkPrerender
*
398 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id
,
400 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
401 i
!= prerenders_
.end(); ++i
) {
402 if (launcher_child_id
== i
->launcher_child_id
&&
403 prerender_id
== i
->prerender_id
) {
410 PrerenderLinkManager::LinkPrerender
*
411 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle
* prerender_handle
) {
412 DCHECK(prerender_handle
);
413 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
414 i
!= prerenders_
.end(); ++i
) {
415 if (prerender_handle
== i
->handle
)
421 void PrerenderLinkManager::RemovePrerender(LinkPrerender
* prerender
) {
422 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
423 i
!= prerenders_
.end(); ++i
) {
424 if (&(*i
) == prerender
) {
425 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
427 prerenders_
.erase(i
);
434 void PrerenderLinkManager::CancelPrerender(LinkPrerender
* prerender
) {
435 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
436 i
!= prerenders_
.end(); ++i
) {
437 if (&(*i
) == prerender
) {
438 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
440 prerenders_
.erase(i
);
442 own_handle
->OnCancel();
449 void PrerenderLinkManager::StartPendingPrerendersForLauncher(
450 PrerenderContents
* launcher
) {
451 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
452 i
!= prerenders_
.end(); ++i
) {
453 if (i
->deferred_launcher
== launcher
)
454 i
->deferred_launcher
= NULL
;
459 void PrerenderLinkManager::CancelPendingPrerendersForLauncher(
460 PrerenderContents
* launcher
) {
461 // Remove all pending prerenders for this launcher.
462 std::vector
<std::list
<LinkPrerender
>::iterator
> to_erase
;
463 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
464 i
!= prerenders_
.end(); ++i
) {
465 if (i
->deferred_launcher
== launcher
) {
467 to_erase
.push_back(i
);
470 std::for_each(to_erase
.begin(), to_erase
.end(),
471 std::bind1st(std::mem_fun(&std::list
<LinkPrerender
>::erase
),
475 void PrerenderLinkManager::Shutdown() {
476 has_shutdown_
= true;
479 // In practice, this is always called from PrerenderLinkManager::OnAddPrerender.
480 void PrerenderLinkManager::OnPrerenderStart(
481 PrerenderHandle
* prerender_handle
) {
482 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
485 Send(prerender
->launcher_child_id
,
486 new PrerenderMsg_OnPrerenderStart(prerender
->prerender_id
));
489 void PrerenderLinkManager::OnPrerenderStopLoading(
490 PrerenderHandle
* prerender_handle
) {
491 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
495 Send(prerender
->launcher_child_id
,
496 new PrerenderMsg_OnPrerenderStopLoading(prerender
->prerender_id
));
499 void PrerenderLinkManager::OnPrerenderDomContentLoaded(
500 PrerenderHandle
* prerender_handle
) {
501 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
505 Send(prerender
->launcher_child_id
,
506 new PrerenderMsg_OnPrerenderDomContentLoaded(prerender
->prerender_id
));
509 void PrerenderLinkManager::OnPrerenderStop(
510 PrerenderHandle
* prerender_handle
) {
511 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
515 // If the prerender became a match complete replacement, the stop
516 // message has already been sent.
517 if (!prerender
->is_match_complete_replacement
) {
518 Send(prerender
->launcher_child_id
,
519 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
521 RemovePrerender(prerender
);
525 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
526 PrerenderHandle
* prerender_handle
) {
527 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
531 DCHECK(!prerender
->is_match_complete_replacement
);
532 prerender
->is_match_complete_replacement
= true;
533 Send(prerender
->launcher_child_id
,
534 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
535 // Do not call RemovePrerender here. The replacement needs to stay connected
536 // to the HTMLLinkElement in the renderer so it notices renderer-triggered
540 } // namespace prerender