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"
12 #include "base/memory/scoped_ptr.h"
13 #include "chrome/browser/prerender/prerender_contents.h"
14 #include "chrome/browser/prerender/prerender_handle.h"
15 #include "chrome/browser/prerender/prerender_manager.h"
16 #include "chrome/browser/prerender/prerender_manager_factory.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/prerender_messages.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/render_view_host.h"
21 #include "content/public/browser/session_storage_namespace.h"
22 #include "content/public/common/referrer.h"
23 #include "ui/gfx/size.h"
26 using base::TimeDelta
;
27 using base::TimeTicks
;
28 using content::RenderViewHost
;
29 using content::SessionStorageNamespace
;
33 void Send(int child_id
, IPC::Message
* raw_message
) {
34 using content::RenderProcessHost
;
35 scoped_ptr
<IPC::Message
> own_message(raw_message
);
37 RenderProcessHost
* render_process_host
= RenderProcessHost::FromID(child_id
);
38 if (!render_process_host
)
40 render_process_host
->Send(own_message
.release());
47 // Helper class to implement PrerenderContents::Observer and watch prerenders
48 // which launch other prerenders.
49 class PrerenderLinkManager::PendingPrerenderManager
50 : public PrerenderContents::Observer
{
52 explicit PendingPrerenderManager(PrerenderLinkManager
* link_manager
)
53 : link_manager_(link_manager
) {}
55 virtual ~PendingPrerenderManager() {
56 DCHECK(observed_launchers_
.empty());
57 for (std::set
<PrerenderContents
*>::iterator i
= observed_launchers_
.begin();
58 i
!= observed_launchers_
.end(); ++i
) {
59 (*i
)->RemoveObserver(this);
63 void ObserveLauncher(PrerenderContents
* launcher
) {
64 DCHECK_EQ(FINAL_STATUS_MAX
, launcher
->final_status());
65 if (observed_launchers_
.find(launcher
) != observed_launchers_
.end())
67 observed_launchers_
.insert(launcher
);
68 launcher
->AddObserver(this);
71 virtual void OnPrerenderStart(PrerenderContents
* launcher
) OVERRIDE
{}
73 virtual void OnPrerenderStop(PrerenderContents
* launcher
) OVERRIDE
{
74 observed_launchers_
.erase(launcher
);
75 if (launcher
->final_status() == FINAL_STATUS_USED
) {
76 link_manager_
->StartPendingPrerendersForLauncher(launcher
);
78 link_manager_
->CancelPendingPrerendersForLauncher(launcher
);
83 // A pointer to the parent PrerenderLinkManager.
84 PrerenderLinkManager
* link_manager_
;
86 // The set of PrerenderContentses being observed. Lifetimes are managed by
88 std::set
<PrerenderContents
*> observed_launchers_
;
91 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager
* manager
)
92 : has_shutdown_(false),
94 pending_prerender_manager_(new PendingPrerenderManager(this)) {}
96 PrerenderLinkManager::~PrerenderLinkManager() {
97 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
98 i
!= prerenders_
.end(); ++i
) {
100 DCHECK(!i
->handle
->IsPrerendering())
101 << "All running prerenders should stop at the same time as the "
102 << "PrerenderManager.";
109 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id
,
112 const content::Referrer
& referrer
,
113 const gfx::Size
& size
,
114 int render_view_route_id
) {
115 DCHECK_EQ(static_cast<LinkPrerender
*>(NULL
),
116 FindByLauncherChildIdAndPrerenderId(launcher_child_id
,
118 content::RenderProcessHost
* rph
=
119 content::RenderProcessHost::FromID(launcher_child_id
);
120 // Guests inside <webview> do not support cross-process navigation and so we
121 // do not allow guests to prerender content.
122 if (rph
&& rph
->IsGuest())
125 // Check if the launcher is itself an unswapped prerender.
126 PrerenderContents
* prerender_contents
=
127 manager_
->GetPrerenderContentsForRoute(launcher_child_id
,
128 render_view_route_id
);
129 if (prerender_contents
&&
130 prerender_contents
->final_status() != FINAL_STATUS_MAX
) {
131 // The launcher is a prerender about to be destroyed asynchronously, but
132 // its AddLinkRelPrerender message raced with shutdown. Ignore it.
133 DCHECK_NE(FINAL_STATUS_USED
, prerender_contents
->final_status());
138 prerender(launcher_child_id
, prerender_id
, url
, referrer
, size
,
139 render_view_route_id
, manager_
->GetCurrentTimeTicks(),
141 prerenders_
.push_back(prerender
);
142 if (prerender_contents
)
143 pending_prerender_manager_
->ObserveLauncher(prerender_contents
);
148 void PrerenderLinkManager::OnCancelPrerender(int child_id
, int prerender_id
) {
149 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
154 CancelPrerender(prerender
);
158 void PrerenderLinkManager::OnAbandonPrerender(int child_id
, int prerender_id
) {
159 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
164 if (!prerender
->handle
) {
165 RemovePrerender(prerender
);
169 prerender
->has_been_abandoned
= true;
170 prerender
->handle
->OnNavigateAway();
171 DCHECK(prerender
->handle
);
173 // If the prerender is not running, remove it from the list so it does not
174 // leak. If it is running, it will send a cancel event when it stops which
176 if (!prerender
->handle
->IsPrerendering())
177 RemovePrerender(prerender
);
180 void PrerenderLinkManager::OnChannelClosing(int child_id
) {
181 std::list
<LinkPrerender
>::iterator next
= prerenders_
.begin();
182 while (next
!= prerenders_
.end()) {
183 std::list
<LinkPrerender
>::iterator it
= next
;
186 if (child_id
!= it
->launcher_child_id
)
189 const size_t running_prerender_count
= CountRunningPrerenders();
190 OnAbandonPrerender(child_id
, it
->prerender_id
);
191 DCHECK_EQ(running_prerender_count
, CountRunningPrerenders());
195 PrerenderLinkManager::LinkPrerender::LinkPrerender(
196 int launcher_child_id
,
199 const content::Referrer
& referrer
,
200 const gfx::Size
& size
,
201 int render_view_route_id
,
202 TimeTicks creation_time
,
203 PrerenderContents
* deferred_launcher
)
204 : launcher_child_id(launcher_child_id
),
205 prerender_id(prerender_id
),
209 render_view_route_id(render_view_route_id
),
210 creation_time(creation_time
),
211 deferred_launcher(deferred_launcher
),
213 is_match_complete_replacement(false),
214 has_been_abandoned(false) {
217 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
218 DCHECK_EQ(static_cast<PrerenderHandle
*>(NULL
), handle
)
219 << "The PrerenderHandle should be destroyed before its Prerender.";
222 bool PrerenderLinkManager::IsEmpty() const {
223 return prerenders_
.empty();
226 size_t PrerenderLinkManager::CountRunningPrerenders() const {
228 for (std::list
<LinkPrerender
>::const_iterator i
= prerenders_
.begin();
229 i
!= prerenders_
.end(); ++i
) {
230 if (i
->handle
&& i
->handle
->IsPrerendering())
236 void PrerenderLinkManager::StartPrerenders() {
240 size_t total_started_prerender_count
= 0;
241 std::list
<LinkPrerender
*> abandoned_prerenders
;
242 std::list
<std::list
<LinkPrerender
>::iterator
> pending_prerenders
;
243 std::multiset
<std::pair
<int, int> >
244 running_launcher_and_render_view_routes
;
246 // Scan the list, counting how many prerenders have handles (and so were added
247 // to the PrerenderManager). The count is done for the system as a whole, and
248 // also per launcher.
249 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
250 i
!= prerenders_
.end(); ++i
) {
251 // Skip prerenders launched by a prerender.
252 if (i
->deferred_launcher
)
255 pending_prerenders
.push_back(i
);
257 ++total_started_prerender_count
;
258 if (i
->has_been_abandoned
) {
259 abandoned_prerenders
.push_back(&(*i
));
261 // We do not count abandoned prerenders towards their launcher, since it
262 // has already navigated on to another page.
263 std::pair
<int, int> launcher_and_render_view_route(
264 i
->launcher_child_id
, i
->render_view_route_id
);
265 running_launcher_and_render_view_routes
.insert(
266 launcher_and_render_view_route
);
267 DCHECK_GE(manager_
->config().max_link_concurrency_per_launcher
,
268 running_launcher_and_render_view_routes
.count(
269 launcher_and_render_view_route
));
273 DCHECK_EQ(&(*i
), FindByLauncherChildIdAndPrerenderId(i
->launcher_child_id
,
276 DCHECK_LE(abandoned_prerenders
.size(), total_started_prerender_count
);
277 DCHECK_GE(manager_
->config().max_link_concurrency
,
278 total_started_prerender_count
);
279 DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count
);
281 TimeTicks now
= manager_
->GetCurrentTimeTicks();
283 // Scan the pending prerenders, starting prerenders as we can.
284 for (std::list
<std::list
<LinkPrerender
>::iterator
>::const_iterator
285 i
= pending_prerenders
.begin(), end
= pending_prerenders
.end();
287 TimeDelta prerender_age
= now
- (*i
)->creation_time
;
288 if (prerender_age
>= manager_
->config().max_wait_to_launch
) {
289 // This prerender waited too long in the queue before launching.
290 prerenders_
.erase(*i
);
294 std::pair
<int, int> launcher_and_render_view_route(
295 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
);
296 if (manager_
->config().max_link_concurrency_per_launcher
<=
297 running_launcher_and_render_view_routes
.count(
298 launcher_and_render_view_route
)) {
299 // This prerender's launcher is already at its limit.
303 if (total_started_prerender_count
>=
304 manager_
->config().max_link_concurrency
||
305 total_started_prerender_count
>= prerenders_
.size()) {
306 // The system is already at its prerender concurrency limit. Can we kill
307 // an abandoned prerender to make room?
308 if (!abandoned_prerenders
.empty()) {
309 CancelPrerender(abandoned_prerenders
.front());
310 --total_started_prerender_count
;
311 abandoned_prerenders
.pop_front();
317 PrerenderHandle
* handle
= manager_
->AddPrerenderFromLinkRelPrerender(
318 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
,
319 (*i
)->url
, (*i
)->referrer
, (*i
)->size
);
321 // This prerender couldn't be launched, it's gone.
322 prerenders_
.erase(*i
);
326 // We have successfully started a new prerender.
327 (*i
)->handle
= handle
;
328 ++total_started_prerender_count
;
329 handle
->SetObserver(this);
330 if (handle
->IsPrerendering())
331 OnPrerenderStart(handle
);
333 running_launcher_and_render_view_routes
.insert(
334 launcher_and_render_view_route
);
338 PrerenderLinkManager::LinkPrerender
*
339 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id
,
341 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
342 i
!= prerenders_
.end(); ++i
) {
343 if (launcher_child_id
== i
->launcher_child_id
&&
344 prerender_id
== i
->prerender_id
) {
351 PrerenderLinkManager::LinkPrerender
*
352 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle
* prerender_handle
) {
353 DCHECK(prerender_handle
);
354 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
355 i
!= prerenders_
.end(); ++i
) {
356 if (prerender_handle
== i
->handle
)
362 void PrerenderLinkManager::RemovePrerender(LinkPrerender
* prerender
) {
363 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
364 i
!= prerenders_
.end(); ++i
) {
365 if (&(*i
) == prerender
) {
366 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
368 prerenders_
.erase(i
);
375 void PrerenderLinkManager::CancelPrerender(LinkPrerender
* prerender
) {
376 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
377 i
!= prerenders_
.end(); ++i
) {
378 if (&(*i
) == prerender
) {
379 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
381 prerenders_
.erase(i
);
383 own_handle
->OnCancel();
390 void PrerenderLinkManager::StartPendingPrerendersForLauncher(
391 PrerenderContents
* launcher
) {
392 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
393 i
!= prerenders_
.end(); ++i
) {
394 if (i
->deferred_launcher
== launcher
)
395 i
->deferred_launcher
= NULL
;
400 void PrerenderLinkManager::CancelPendingPrerendersForLauncher(
401 PrerenderContents
* launcher
) {
402 // Remove all pending prerenders for this launcher.
403 std::vector
<std::list
<LinkPrerender
>::iterator
> to_erase
;
404 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
405 i
!= prerenders_
.end(); ++i
) {
406 if (i
->deferred_launcher
== launcher
) {
408 to_erase
.push_back(i
);
411 std::for_each(to_erase
.begin(), to_erase
.end(),
412 std::bind1st(std::mem_fun(&std::list
<LinkPrerender
>::erase
),
416 void PrerenderLinkManager::Shutdown() {
417 has_shutdown_
= true;
420 // In practice, this is always called from either
421 // PrerenderLinkManager::OnAddPrerender in the regular case, or in the pending
422 // prerender case, from PrerenderHandle::AdoptPrerenderDataFrom.
423 void PrerenderLinkManager::OnPrerenderStart(
424 PrerenderHandle
* prerender_handle
) {
425 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
428 Send(prerender
->launcher_child_id
,
429 new PrerenderMsg_OnPrerenderStart(prerender
->prerender_id
));
432 void PrerenderLinkManager::OnPrerenderStopLoading(
433 PrerenderHandle
* prerender_handle
) {
434 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
438 Send(prerender
->launcher_child_id
,
439 new PrerenderMsg_OnPrerenderStopLoading(prerender
->prerender_id
));
442 void PrerenderLinkManager::OnPrerenderStop(
443 PrerenderHandle
* prerender_handle
) {
444 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
448 // If the prerender became a match complete replacement, the stop
449 // message has already been sent.
450 if (!prerender
->is_match_complete_replacement
) {
451 Send(prerender
->launcher_child_id
,
452 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
454 RemovePrerender(prerender
);
458 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
459 PrerenderHandle
* prerender_handle
) {
460 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
464 DCHECK(!prerender
->is_match_complete_replacement
);
465 prerender
->is_match_complete_replacement
= true;
466 Send(prerender
->launcher_child_id
,
467 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
468 // Do not call RemovePrerender here. The replacement needs to stay connected
469 // to the HTMLLinkElement in the renderer so it notices renderer-triggered
473 } // namespace prerender