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"
11 #include "base/memory/scoped_ptr.h"
12 #include "chrome/browser/prerender/prerender_contents.h"
13 #include "chrome/browser/prerender/prerender_handle.h"
14 #include "chrome/browser/prerender/prerender_manager.h"
15 #include "chrome/browser/prerender/prerender_manager_factory.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/common/prerender_messages.h"
18 #include "content/public/browser/render_process_host.h"
19 #include "content/public/browser/render_view_host.h"
20 #include "content/public/browser/session_storage_namespace.h"
21 #include "content/public/common/referrer.h"
22 #include "ui/gfx/size.h"
25 using base::TimeDelta
;
26 using base::TimeTicks
;
27 using content::RenderViewHost
;
28 using content::SessionStorageNamespace
;
32 void Send(int child_id
, IPC::Message
* raw_message
) {
33 using content::RenderProcessHost
;
34 scoped_ptr
<IPC::Message
> own_message(raw_message
);
36 RenderProcessHost
* render_process_host
= RenderProcessHost::FromID(child_id
);
37 if (!render_process_host
)
39 render_process_host
->Send(own_message
.release());
46 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager
* manager
)
47 : has_shutdown_(false),
51 PrerenderLinkManager::~PrerenderLinkManager() {
52 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
53 i
!= prerenders_
.end(); ++i
) {
55 DCHECK(!i
->handle
->IsPrerendering())
56 << "All running prerenders should stop at the same time as the "
57 << "PrerenderManager.";
64 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id
,
67 const content::Referrer
& referrer
,
68 const gfx::Size
& size
,
69 int render_view_route_id
) {
70 DCHECK_EQ(static_cast<LinkPrerender
*>(NULL
),
71 FindByLauncherChildIdAndPrerenderId(launcher_child_id
,
73 content::RenderProcessHost
* rph
=
74 content::RenderProcessHost::FromID(launcher_child_id
);
75 // Guests inside <webview> do not support cross-process navigation and so we
76 // do not allow guests to prerender content.
77 if (rph
&& rph
->IsGuest())
81 prerender(launcher_child_id
, prerender_id
, url
, referrer
, size
,
82 render_view_route_id
, manager_
->GetCurrentTimeTicks());
83 prerenders_
.push_back(prerender
);
87 void PrerenderLinkManager::OnCancelPrerender(int child_id
, int prerender_id
) {
88 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
93 CancelPrerender(prerender
);
97 void PrerenderLinkManager::OnAbandonPrerender(int child_id
, int prerender_id
) {
98 LinkPrerender
* prerender
= FindByLauncherChildIdAndPrerenderId(child_id
,
103 if (!prerender
->handle
) {
104 RemovePrerender(prerender
);
108 prerender
->has_been_abandoned
= true;
109 prerender
->handle
->OnNavigateAway();
110 DCHECK(prerender
->handle
);
112 // If the prerender is not running, remove it from the list so it does not
113 // leak. If it is running, it will send a cancel event when it stops which
115 if (!prerender
->handle
->IsPrerendering())
116 RemovePrerender(prerender
);
119 void PrerenderLinkManager::OnChannelClosing(int child_id
) {
120 std::list
<LinkPrerender
>::iterator next
= prerenders_
.begin();
121 while (next
!= prerenders_
.end()) {
122 std::list
<LinkPrerender
>::iterator it
= next
;
125 if (child_id
!= it
->launcher_child_id
)
128 const size_t running_prerender_count
= CountRunningPrerenders();
129 OnAbandonPrerender(child_id
, it
->prerender_id
);
130 DCHECK_EQ(running_prerender_count
, CountRunningPrerenders());
134 PrerenderLinkManager::LinkPrerender::LinkPrerender(
135 int launcher_child_id
,
138 const content::Referrer
& referrer
,
139 const gfx::Size
& size
,
140 int render_view_route_id
,
141 TimeTicks creation_time
) : launcher_child_id(launcher_child_id
),
142 prerender_id(prerender_id
),
146 render_view_route_id(render_view_route_id
),
147 creation_time(creation_time
),
149 is_match_complete_replacement(false),
150 has_been_abandoned(false) {
153 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
154 DCHECK_EQ(static_cast<PrerenderHandle
*>(NULL
), handle
)
155 << "The PrerenderHandle should be destroyed before its Prerender.";
158 bool PrerenderLinkManager::IsEmpty() const {
159 return prerenders_
.empty();
162 size_t PrerenderLinkManager::CountRunningPrerenders() const {
164 for (std::list
<LinkPrerender
>::const_iterator i
= prerenders_
.begin();
165 i
!= prerenders_
.end(); ++i
) {
166 if (i
->handle
&& i
->handle
->IsPrerendering())
172 void PrerenderLinkManager::StartPrerenders() {
176 size_t total_started_prerender_count
= 0;
177 std::list
<LinkPrerender
*> abandoned_prerenders
;
178 std::list
<std::list
<LinkPrerender
>::iterator
> pending_prerenders
;
179 std::multiset
<std::pair
<int, int> >
180 running_launcher_and_render_view_routes
;
182 // Scan the list, counting how many prerenders have handles (and so were added
183 // to the PrerenderManager). The count is done for the system as a whole, and
184 // also per launcher.
185 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
186 i
!= prerenders_
.end(); ++i
) {
188 pending_prerenders
.push_back(i
);
190 ++total_started_prerender_count
;
191 if (i
->has_been_abandoned
) {
192 abandoned_prerenders
.push_back(&(*i
));
194 // We do not count abandoned prerenders towards their launcher, since it
195 // has already navigated on to another page.
196 std::pair
<int, int> launcher_and_render_view_route(
197 i
->launcher_child_id
, i
->render_view_route_id
);
198 running_launcher_and_render_view_routes
.insert(
199 launcher_and_render_view_route
);
200 DCHECK_GE(manager_
->config().max_link_concurrency_per_launcher
,
201 running_launcher_and_render_view_routes
.count(
202 launcher_and_render_view_route
));
206 DCHECK_EQ(&(*i
), FindByLauncherChildIdAndPrerenderId(i
->launcher_child_id
,
209 DCHECK_LE(abandoned_prerenders
.size(), total_started_prerender_count
);
210 DCHECK_GE(manager_
->config().max_link_concurrency
,
211 total_started_prerender_count
);
212 DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count
);
214 TimeTicks now
= manager_
->GetCurrentTimeTicks();
216 // Scan the pending prerenders, starting prerenders as we can.
217 for (std::list
<std::list
<LinkPrerender
>::iterator
>::const_iterator
218 i
= pending_prerenders
.begin(), end
= pending_prerenders
.end();
220 TimeDelta prerender_age
= now
- (*i
)->creation_time
;
221 if (prerender_age
>= manager_
->config().max_wait_to_launch
) {
222 // This prerender waited too long in the queue before launching.
223 prerenders_
.erase(*i
);
227 std::pair
<int, int> launcher_and_render_view_route(
228 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
);
229 if (manager_
->config().max_link_concurrency_per_launcher
<=
230 running_launcher_and_render_view_routes
.count(
231 launcher_and_render_view_route
)) {
232 // This prerender's launcher is already at its limit.
236 if (total_started_prerender_count
>=
237 manager_
->config().max_link_concurrency
||
238 total_started_prerender_count
>= prerenders_
.size()) {
239 // The system is already at its prerender concurrency limit. Can we kill
240 // an abandoned prerender to make room?
241 if (!abandoned_prerenders
.empty()) {
242 CancelPrerender(abandoned_prerenders
.front());
243 --total_started_prerender_count
;
244 abandoned_prerenders
.pop_front();
250 PrerenderHandle
* handle
= manager_
->AddPrerenderFromLinkRelPrerender(
251 (*i
)->launcher_child_id
, (*i
)->render_view_route_id
,
252 (*i
)->url
, (*i
)->referrer
, (*i
)->size
);
254 // This prerender couldn't be launched, it's gone.
255 prerenders_
.erase(*i
);
259 // We have successfully started a new prerender.
260 (*i
)->handle
= handle
;
261 ++total_started_prerender_count
;
262 handle
->SetObserver(this);
263 if (handle
->IsPrerendering())
264 OnPrerenderStart(handle
);
266 running_launcher_and_render_view_routes
.insert(
267 launcher_and_render_view_route
);
271 PrerenderLinkManager::LinkPrerender
*
272 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id
,
274 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
275 i
!= prerenders_
.end(); ++i
) {
276 if (launcher_child_id
== i
->launcher_child_id
&&
277 prerender_id
== i
->prerender_id
) {
284 PrerenderLinkManager::LinkPrerender
*
285 PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle
* prerender_handle
) {
286 DCHECK(prerender_handle
);
287 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
288 i
!= prerenders_
.end(); ++i
) {
289 if (prerender_handle
== i
->handle
)
295 void PrerenderLinkManager::RemovePrerender(LinkPrerender
* prerender
) {
296 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
297 i
!= prerenders_
.end(); ++i
) {
298 if (&(*i
) == prerender
) {
299 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
301 prerenders_
.erase(i
);
308 void PrerenderLinkManager::CancelPrerender(LinkPrerender
* prerender
) {
309 for (std::list
<LinkPrerender
>::iterator i
= prerenders_
.begin();
310 i
!= prerenders_
.end(); ++i
) {
311 if (&(*i
) == prerender
) {
312 scoped_ptr
<PrerenderHandle
> own_handle(i
->handle
);
314 prerenders_
.erase(i
);
316 own_handle
->OnCancel();
323 void PrerenderLinkManager::Shutdown() {
324 has_shutdown_
= true;
327 // In practice, this is always called from either
328 // PrerenderLinkManager::OnAddPrerender in the regular case, or in the pending
329 // prerender case, from PrerenderHandle::AdoptPrerenderDataFrom.
330 void PrerenderLinkManager::OnPrerenderStart(
331 PrerenderHandle
* prerender_handle
) {
332 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
335 Send(prerender
->launcher_child_id
,
336 new PrerenderMsg_OnPrerenderStart(prerender
->prerender_id
));
339 void PrerenderLinkManager::OnPrerenderStopLoading(
340 PrerenderHandle
* prerender_handle
) {
341 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
345 Send(prerender
->launcher_child_id
,
346 new PrerenderMsg_OnPrerenderStopLoading(prerender
->prerender_id
));
349 void PrerenderLinkManager::OnPrerenderStop(
350 PrerenderHandle
* prerender_handle
) {
351 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
355 // If the prerender became a match complete replacement, the stop
356 // message has already been sent.
357 if (!prerender
->is_match_complete_replacement
) {
358 Send(prerender
->launcher_child_id
,
359 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
361 RemovePrerender(prerender
);
365 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
366 PrerenderHandle
* prerender_handle
) {
367 LinkPrerender
* prerender
= FindByPrerenderHandle(prerender_handle
);
371 DCHECK(!prerender
->is_match_complete_replacement
);
372 prerender
->is_match_complete_replacement
= true;
373 Send(prerender
->launcher_child_id
,
374 new PrerenderMsg_OnPrerenderStop(prerender
->prerender_id
));
375 // Do not call RemovePrerender here. The replacement needs to stay connected
376 // to the HTMLLinkElement in the renderer so it notices renderer-triggered
380 } // namespace prerender