Roll src/third_party/WebKit f298044:aa8346d (svn 202628:202629)
[chromium-blink-merge.git] / chrome / browser / prerender / prerender_link_manager.cc
blobd4d379729cc55ad7f1b50d47ec6c0a588d963c6c
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"
7 #include <functional>
8 #include <limits>
9 #include <set>
10 #include <string>
11 #include <utility>
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/geometry/size.h"
28 #include "url/gurl.h"
30 #if defined(ENABLE_EXTENSIONS)
31 #include "components/guest_view/browser/guest_view_base.h"
32 #endif
34 using base::TimeDelta;
35 using base::TimeTicks;
36 using content::RenderViewHost;
37 using content::SessionStorageNamespace;
39 namespace prerender {
41 namespace {
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) {
55 return true;
56 } else if (should_start_rel_next_prerenders &&
57 (rel_types & PrerenderRelTypeNext) == PrerenderRelTypeNext) {
58 return true;
60 return false;
63 static_assert(PrerenderRelTypePrerender == 0x1,
64 "RelTypeHistogrameEnum must match PrerenderRelType");
65 static_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)
94 return;
95 render_process_host->Send(own_message.release());
98 } // namespace
100 // Helper class to implement PrerenderContents::Observer and watch prerenders
101 // which launch other prerenders.
102 class PrerenderLinkManager::PendingPrerenderManager
103 : public PrerenderContents::Observer {
104 public:
105 explicit PendingPrerenderManager(PrerenderLinkManager* link_manager)
106 : link_manager_(link_manager) {}
108 ~PendingPrerenderManager() override {
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())
119 return;
120 observed_launchers_.insert(launcher);
121 launcher->AddObserver(this);
124 void OnPrerenderStart(PrerenderContents* launcher) override {}
126 void OnPrerenderStop(PrerenderContents* launcher) override {
127 observed_launchers_.erase(launcher);
128 if (launcher->final_status() == FINAL_STATUS_USED) {
129 link_manager_->StartPendingPrerendersForLauncher(launcher);
130 } else {
131 link_manager_->CancelPendingPrerendersForLauncher(launcher);
135 private:
136 // A pointer to the parent PrerenderLinkManager.
137 PrerenderLinkManager* link_manager_;
139 // The set of PrerenderContentses being observed. Lifetimes are managed by
140 // OnPrerenderStop.
141 std::set<PrerenderContents*> observed_launchers_;
144 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager)
145 : has_shutdown_(false),
146 manager_(manager),
147 pending_prerender_manager_(new PendingPrerenderManager(this)) {}
149 PrerenderLinkManager::~PrerenderLinkManager() {
150 for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
151 i != prerenders_.end(); ++i) {
152 if (i->handle) {
153 DCHECK(!i->handle->IsPrerendering())
154 << "All running prerenders should stop at the same time as the "
155 << "PrerenderManager.";
156 delete i->handle;
157 i->handle = 0;
162 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id,
163 int prerender_id,
164 const GURL& url,
165 uint32 rel_types,
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,
171 prerender_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 (guest_view::GuestViewBase::IsGuest(web_contents))
181 return;
182 #endif
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());
193 return;
196 LinkPrerender
197 prerender(launcher_child_id, prerender_id, url, rel_types, referrer, size,
198 render_view_route_id, manager_->GetCurrentTimeTicks(),
199 prerender_contents);
200 prerenders_.push_back(prerender);
201 RecordLinkManagerAdded(rel_types);
202 if (prerender_contents)
203 pending_prerender_manager_->ObserveLauncher(prerender_contents);
204 else
205 StartPrerenders();
208 void PrerenderLinkManager::OnCancelPrerender(int child_id, int prerender_id) {
209 LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
210 prerender_id);
211 if (!prerender)
212 return;
214 CancelPrerender(prerender);
215 StartPrerenders();
218 void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
219 LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
220 prerender_id);
221 if (!prerender)
222 return;
224 if (!prerender->handle) {
225 RemovePrerender(prerender);
226 return;
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
235 // will remove it.
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;
244 ++next;
246 if (child_id != it->launcher_child_id)
247 continue;
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,
257 int prerender_id,
258 const GURL& url,
259 uint32 rel_types,
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),
267 url(url),
268 rel_types(rel_types),
269 referrer(referrer),
270 size(size),
271 render_view_route_id(render_view_route_id),
272 creation_time(creation_time),
273 deferred_launcher(deferred_launcher),
274 handle(NULL),
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 {
289 size_t retval = 0;
290 for (std::list<LinkPrerender>::const_iterator i = prerenders_.begin();
291 i != prerenders_.end(); ++i) {
292 if (i->handle && i->handle->IsPrerendering())
293 ++retval;
295 return retval;
298 void PrerenderLinkManager::StartPrerenders() {
299 if (has_shutdown_)
300 return;
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)
315 continue;
316 if (!i->handle) {
317 pending_prerenders.push_back(i);
318 } else {
319 ++total_started_prerender_count;
320 if (i->has_been_abandoned) {
321 abandoned_prerenders.push_back(&(*i));
322 } else {
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,
336 i->prerender_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();
348 i != end; ++i) {
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);
353 continue;
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.
362 continue;
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();
374 } else {
375 return;
379 if (!ShouldStartPrerender((*i)->rel_types)) {
380 prerenders_.erase(*i);
381 continue;
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);
387 if (!handle) {
388 // This prerender couldn't be launched, it's gone.
389 prerenders_.erase(*i);
390 continue;
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,
408 int prerender_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) {
413 return &(*i);
416 return NULL;
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)
425 return &(*i);
427 return NULL;
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);
435 i->handle = NULL;
436 prerenders_.erase(i);
437 return;
440 NOTREACHED();
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);
448 i->handle = NULL;
449 prerenders_.erase(i);
450 if (own_handle)
451 own_handle->OnCancel();
452 return;
455 NOTREACHED();
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;
465 StartPrerenders();
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) {
475 DCHECK(!i->handle);
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),
481 &prerenders_));
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);
492 if (!prerender)
493 return;
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);
501 if (!prerender)
502 return;
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);
511 if (!prerender)
512 return;
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);
521 if (!prerender)
522 return;
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);
531 StartPrerenders();
534 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
535 PrerenderHandle* prerender_handle) {
536 LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
537 if (!prerender)
538 return;
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
546 // cancelations.
549 } // namespace prerender