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/guest_view/guest_view_base.h"
17 #include "chrome/browser/prerender/prerender_contents.h"
18 #include "chrome/browser/prerender/prerender_handle.h"
19 #include "chrome/browser/prerender/prerender_manager.h"
20 #include "chrome/browser/prerender/prerender_manager_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/common/prerender_messages.h"
23 #include "chrome/common/prerender_types.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/render_view_host.h"
26 #include "content/public/browser/session_storage_namespace.h"
27 #include "content/public/common/referrer.h"
28 #include "ui/gfx/size.h"
31 using base::TimeDelta
;
32 using base::TimeTicks
;
33 using content::RenderViewHost
;
34 using content::SessionStorageNamespace
;
40 bool ShouldStartRelNextPrerenders() {
41 const std::string experiment_name
=
42 base::FieldTrialList::FindFullName("PrerenderRelNextTrial");
44 return experiment_name
.find("Yes") != std::string::npos
;
47 bool ShouldStartPrerender(const uint32 rel_types
) {
48 const bool should_start_rel_next_prerenders
=
49 ShouldStartRelNextPrerenders();
51 if (rel_types
& PrerenderRelTypePrerender
) {
53 } else if (should_start_rel_next_prerenders
&&
54 (rel_types
& PrerenderRelTypeNext
) == PrerenderRelTypeNext
) {
60 COMPILE_ASSERT(PrerenderRelTypePrerender
== 0x1,
61 RelTypeHistogramEnum_must_match_PrerenderRelType
);
62 COMPILE_ASSERT(PrerenderRelTypeNext
== 0x2,
63 RelTypeHistogramEnum_must_match_PrerenderRelType
);
64 enum RelTypeHistogramEnum
{
65 RelTypeHistogramEnumNone
= 0,
66 RelTypeHistogramEnumPrerender
= PrerenderRelTypePrerender
,
67 RelTypeHistogramEnumNext
= PrerenderRelTypeNext
,
68 RelTypeHistogramEnumPrerenderAndNext
=
69 PrerenderRelTypePrerender
| PrerenderRelTypeNext
,
70 RelTypeHistogramEnumMax
,
73 void RecordLinkManagerAdded(const uint32 rel_types
) {
74 const uint32 enum_value
= rel_types
& (RelTypeHistogramEnumMax
- 1);
75 UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkAdded", enum_value
,
76 RelTypeHistogramEnumMax
);
79 void RecordLinkManagerStarting(const uint32 rel_types
) {
80 const uint32 enum_value
= rel_types
& (RelTypeHistogramEnumMax
- 1);
81 UMA_HISTOGRAM_ENUMERATION("Prerender.RelTypesLinkStarted", enum_value
,
82 RelTypeHistogramEnumMax
);
85 void Send(int child_id
, IPC::Message
* raw_message
) {
86 using content::RenderProcessHost
;
87 scoped_ptr
<IPC::Message
> own_message(raw_message
);
89 RenderProcessHost
* render_process_host
= RenderProcessHost::FromID(child_id
);
90 if (!render_process_host
)
92 render_process_host
->Send(own_message
.release());
97 // Helper class to implement PrerenderContents::Observer and watch prerenders
98 // which launch other prerenders.
99 class PrerenderLinkManager::PendingPrerenderManager
100 : public PrerenderContents::Observer
{
102 explicit PendingPrerenderManager(PrerenderLinkManager
* link_manager
)
103 : link_manager_(link_manager
) {}
105 virtual ~PendingPrerenderManager() {
106 DCHECK(observed_launchers_
.empty());
107 for (std::set
<PrerenderContents
*>::iterator i
= observed_launchers_
.begin();
108 i
!= observed_launchers_
.end(); ++i
) {
109 (*i
)->RemoveObserver(this);
113 void ObserveLauncher(PrerenderContents
* launcher
) {
114 DCHECK_EQ(FINAL_STATUS_MAX
, launcher
->final_status());
115 if (observed_launchers_
.find(launcher
) != observed_launchers_
.end())
117 observed_launchers_
.insert(launcher
);
118 launcher
->AddObserver(this);
121 virtual void OnPrerenderStart(PrerenderContents
* launcher
) OVERRIDE
{}
123 virtual void OnPrerenderStop(PrerenderContents
* launcher
) OVERRIDE
{
124 observed_launchers_
.erase(launcher
);
125 if (launcher
->final_status() == FINAL_STATUS_USED
) {
126 link_manager_
->StartPendingPrerendersForLauncher(launcher
);
128 link_manager_
->CancelPendingPrerendersForLauncher(launcher
);
133 // A pointer to the parent PrerenderLinkManager.
134 PrerenderLinkManager
* link_manager_
;
136 // The set of PrerenderContentses being observed. Lifetimes are managed by
138 std::set
<PrerenderContents
*> observed_launchers_
;
141 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager
* manager
)
142 : has_shutdown_(false),
144 pending_prerender_manager_(new PendingPrerenderManager(this)) {}
146 PrerenderLinkManager::~PrerenderLinkManager() {
147 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
148 i
!= prerenders_
.end(); ++i
) {
150 DCHECK(!i
->handle
->IsPrerendering())
151 << "All running prerenders should stop at the same time as the "
152 << "PrerenderManager.";
159 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id
,
163 const content::Referrer
& referrer
,
164 const gfx::Size
& size
,
165 int render_view_route_id
) {
166 DCHECK_EQ(static_cast<LinkPrerender
*>(NULL
),
167 FindByLauncherChildIdAndPrerenderId(launcher_child_id
,
169 content::RenderViewHost
* rvh
=
170 content::RenderViewHost::FromID(launcher_child_id
, render_view_route_id
);
171 content::WebContents
* web_contents
=
172 rvh
? content::WebContents::FromRenderViewHost(rvh
) : NULL
;
173 // Guests inside <webview> do not support cross-process navigation and so we
174 // do not allow guests to prerender content.
175 if (GuestViewBase::IsGuest(web_contents
))
178 // Check if the launcher is itself an unswapped prerender.
179 PrerenderContents
* prerender_contents
=
180 manager_
->GetPrerenderContentsForRoute(launcher_child_id
,
181 render_view_route_id
);
182 if (prerender_contents
&&
183 prerender_contents
->final_status() != FINAL_STATUS_MAX
) {
184 // The launcher is a prerender about to be destroyed asynchronously, but
185 // its AddLinkRelPrerender message raced with shutdown. Ignore it.
186 DCHECK_NE(FINAL_STATUS_USED
, prerender_contents
->final_status());
191 prerender(launcher_child_id
, prerender_id
, url
, rel_types
, referrer
, size
,
192 render_view_route_id
, manager_
->GetCurrentTimeTicks(),
194 prerenders_
.push_back(prerender
);
195 RecordLinkManagerAdded(rel_types
);
196 if (prerender_contents
)
197 pending_prerender_manager_
->ObserveLauncher(prerender_contents
);
202 void PrerenderLinkManager::OnCancelPrerender(int child_id
, int prerender_id
) {
203 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
208 CancelPrerender(prerender
);
212 void PrerenderLinkManager::OnAbandonPrerender(int child_id
, int prerender_id
) {
213 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
218 if (!prerender
->handle
) {
219 RemovePrerender(prerender
);
223 prerender
->has_been_abandoned
= true;
224 prerender
->handle
->OnNavigateAway();
225 DCHECK(prerender
->handle
);
227 // If the prerender is not running, remove it from the list so it does not
228 // leak. If it is running, it will send a cancel event when it stops which
230 if (!prerender
->handle
->IsPrerendering())
231 RemovePrerender(prerender
);
234 void PrerenderLinkManager::OnChannelClosing(int child_id
) {
235 std::list
<LinkPrerender
>::iterator next
= prerenders_
.begin();
236 while (next
!= prerenders_
.end()) {
237 std::list
<LinkPrerender
>::iterator it
= next
;
240 if (child_id
!= it
->launcher_child_id
)
243 const size_t running_prerender_count
= CountRunningPrerenders();
244 OnAbandonPrerender(child_id
, it
->prerender_id
);
245 DCHECK_EQ(running_prerender_count
, CountRunningPrerenders());
249 PrerenderLinkManager::LinkPrerender::LinkPrerender(
250 int launcher_child_id
,
254 const content::Referrer
& referrer
,
255 const gfx::Size
& size
,
256 int render_view_route_id
,
257 TimeTicks creation_time
,
258 PrerenderContents
* deferred_launcher
)
259 : launcher_child_id(launcher_child_id
),
260 prerender_id(prerender_id
),
262 rel_types(rel_types
),
265 render_view_route_id(render_view_route_id
),
266 creation_time(creation_time
),
267 deferred_launcher(deferred_launcher
),
269 is_match_complete_replacement(false),
270 has_been_abandoned(false) {
273 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
274 DCHECK_EQ(static_cast<PrerenderHandle
*>(NULL
), handle
)
275 << "The PrerenderHandle should be destroyed before its Prerender.";
278 bool PrerenderLinkManager::IsEmpty() const {
279 return prerenders_
.empty();
282 size_t PrerenderLinkManager::CountRunningPrerenders() const {
284 for (std::list
<LinkPrerender
>::const_iterator i
= prerenders_
.begin();
285 i
!= prerenders_
.end(); ++i
) {
286 if (i
->handle
&& i
->handle
->IsPrerendering())
292 void PrerenderLinkManager::StartPrerenders() {
296 size_t total_started_prerender_count
= 0;
297 std::list
<LinkPrerender
*> abandoned_prerenders
;
298 std::list
<std::list
<LinkPrerender
>::iterator
> pending_prerenders
;
299 std::multiset
<std::pair
<int, int> >
300 running_launcher_and_render_view_routes
;
302 // Scan the list, counting how many prerenders have handles (and so were added
303 // to the PrerenderManager). The count is done for the system as a whole, and
304 // also per launcher.
305 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
306 i
!= prerenders_
.end(); ++i
) {
307 // Skip prerenders launched by a prerender.
308 if (i
->deferred_launcher
)
311 pending_prerenders
.push_back(i
);
313 ++total_started_prerender_count
;
314 if (i
->has_been_abandoned
) {
315 abandoned_prerenders
.push_back(&(*i
));
317 // We do not count abandoned prerenders towards their launcher, since it
318 // has already navigated on to another page.
319 std::pair
<int, int> launcher_and_render_view_route(
320 i
->launcher_child_id
, i
->render_view_route_id
);
321 running_launcher_and_render_view_routes
.insert(
322 launcher_and_render_view_route
);
323 DCHECK_GE(manager_
->config().max_link_concurrency_per_launcher
,
324 running_launcher_and_render_view_routes
.count(
325 launcher_and_render_view_route
));
329 DCHECK_EQ(&(*i
), FindByLauncherChildIdAndPrerenderId(i
->launcher_child_id
,
332 DCHECK_LE(abandoned_prerenders
.size(), total_started_prerender_count
);
333 DCHECK_GE(manager_
->config().max_link_concurrency
,
334 total_started_prerender_count
);
335 DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count
);
337 TimeTicks now
= manager_
->GetCurrentTimeTicks();
339 // Scan the pending prerenders, starting prerenders as we can.
340 for (std::list
<std::list
<LinkPrerender
>::iterator
>::const_iterator
341 i
= pending_prerenders
.begin(), end
= pending_prerenders
.end();
343 TimeDelta prerender_age
= now
- (*i
)->creation_time
;
344 if (prerender_age
>= manager_
->config().max_wait_to_launch
) {
345 // This prerender waited too long in the queue before launching.
346 prerenders_
.erase(*i
);
350 std::pair
<int, int> launcher_and_render_view_route(
351 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
);
352 if (manager_
->config().max_link_concurrency_per_launcher
<=
353 running_launcher_and_render_view_routes
.count(
354 launcher_and_render_view_route
)) {
355 // This prerender's launcher is already at its limit.
359 if (total_started_prerender_count
>=
360 manager_
->config().max_link_concurrency
||
361 total_started_prerender_count
>= prerenders_
.size()) {
362 // The system is already at its prerender concurrency limit. Can we kill
363 // an abandoned prerender to make room?
364 if (!abandoned_prerenders
.empty()) {
365 CancelPrerender(abandoned_prerenders
.front());
366 --total_started_prerender_count
;
367 abandoned_prerenders
.pop_front();
373 if (!ShouldStartPrerender((*i
)->rel_types
)) {
374 prerenders_
.erase(*i
);
378 PrerenderHandle
* handle
= manager_
->AddPrerenderFromLinkRelPrerender(
379 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
,
380 (*i
)->url
, (*i
)->rel_types
, (*i
)->referrer
, (*i
)->size
);
382 // This prerender couldn't be launched, it's gone.
383 prerenders_
.erase(*i
);
387 // We have successfully started a new prerender.
388 (*i
)->handle
= handle
;
389 ++total_started_prerender_count
;
390 handle
->SetObserver(this);
391 if (handle
->IsPrerendering())
392 OnPrerenderStart(handle
);
393 RecordLinkManagerStarting((*i
)->rel_types
);
395 running_launcher_and_render_view_routes
.insert(
396 launcher_and_render_view_route
);
400 PrerenderLinkManager::LinkPrerender
*
401 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id
,
403 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
404 i
!= prerenders_
.end(); ++i
) {
405 if (launcher_child_id
== i
->launcher_child_id
&&
406 prerender_id
== i
->prerender_id
) {
413 PrerenderLinkManager::LinkPrerender
*
414 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle
* prerender_handle
) {
415 DCHECK(prerender_handle
);
416 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
417 i
!= prerenders_
.end(); ++i
) {
418 if (prerender_handle
== i
->handle
)
424 void PrerenderLinkManager::RemovePrerender(LinkPrerender
* prerender
) {
425 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
426 i
!= prerenders_
.end(); ++i
) {
427 if (&(*i
) == prerender
) {
428 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
430 prerenders_
.erase(i
);
437 void PrerenderLinkManager::CancelPrerender(LinkPrerender
* prerender
) {
438 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
439 i
!= prerenders_
.end(); ++i
) {
440 if (&(*i
) == prerender
) {
441 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
443 prerenders_
.erase(i
);
445 own_handle
->OnCancel();
452 void PrerenderLinkManager::StartPendingPrerendersForLauncher(
453 PrerenderContents
* launcher
) {
454 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
455 i
!= prerenders_
.end(); ++i
) {
456 if (i
->deferred_launcher
== launcher
)
457 i
->deferred_launcher
= NULL
;
462 void PrerenderLinkManager::CancelPendingPrerendersForLauncher(
463 PrerenderContents
* launcher
) {
464 // Remove all pending prerenders for this launcher.
465 std::vector
<std::list
<LinkPrerender
>::iterator
> to_erase
;
466 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
467 i
!= prerenders_
.end(); ++i
) {
468 if (i
->deferred_launcher
== launcher
) {
470 to_erase
.push_back(i
);
473 std::for_each(to_erase
.begin(), to_erase
.end(),
474 std::bind1st(std::mem_fun(&std::list
<LinkPrerender
>::erase
),
478 void PrerenderLinkManager::Shutdown() {
479 has_shutdown_
= true;
482 // In practice, this is always called from PrerenderLinkManager::OnAddPrerender.
483 void PrerenderLinkManager::OnPrerenderStart(
484 PrerenderHandle
* prerender_handle
) {
485 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
488 Send(prerender
->launcher_child_id
,
489 new PrerenderMsg_OnPrerenderStart(prerender
->prerender_id
));
492 void PrerenderLinkManager::OnPrerenderStopLoading(
493 PrerenderHandle
* prerender_handle
) {
494 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
498 Send(prerender
->launcher_child_id
,
499 new PrerenderMsg_OnPrerenderStopLoading(prerender
->prerender_id
));
502 void PrerenderLinkManager::OnPrerenderDomContentLoaded(
503 PrerenderHandle
* prerender_handle
) {
504 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
508 Send(prerender
->launcher_child_id
,
509 new PrerenderMsg_OnPrerenderDomContentLoaded(prerender
->prerender_id
));
512 void PrerenderLinkManager::OnPrerenderStop(
513 PrerenderHandle
* prerender_handle
) {
514 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
518 // If the prerender became a match complete replacement, the stop
519 // message has already been sent.
520 if (!prerender
->is_match_complete_replacement
) {
521 Send(prerender
->launcher_child_id
,
522 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
524 RemovePrerender(prerender
);
528 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
529 PrerenderHandle
* prerender_handle
) {
530 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
534 DCHECK(!prerender
->is_match_complete_replacement
);
535 prerender
->is_match_complete_replacement
= true;
536 Send(prerender
->launcher_child_id
,
537 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
538 // Do not call RemovePrerender here. The replacement needs to stay connected
539 // to the HTMLLinkElement in the renderer so it notices renderer-triggered
543 } // namespace prerender