Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / prerender / prerender_link_manager.cc
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.
4
5 #include "chrome/browser/prerender/prerender_link_manager.h"
6
7 #include <functional>
8 #include <limits>
9 #include <set>
10 #include <string>
11 #include <utility>
12
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"
28 #include "url/gurl.h"
29
30 using base::TimeDelta;
31 using base::TimeTicks;
32 using content::RenderViewHost;
33 using content::SessionStorageNamespace;
34
35 namespace prerender {
36
37 namespace {
38
39 bool ShouldStartRelNextPrerenders() {
40   const std::string experiment_name =
41       base::FieldTrialList::FindFullName("PrerenderRelNextTrial");
42
43   return experiment_name.find("Yes") != std::string::npos;
44 }
45
46 bool ShouldStartPrerender(const uint32 rel_types) {
47   const bool should_start_rel_next_prerenders =
48       ShouldStartRelNextPrerenders();
49
50   if (rel_types & PrerenderRelTypePrerender) {
51     return true;
52   } else if (should_start_rel_next_prerenders &&
53              (rel_types & PrerenderRelTypeNext) == PrerenderRelTypeNext) {
54     return true;
55   }
56   return false;
57 }
58
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,
70 };
71
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);
76 }
77
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);
82 }
83
84 void Send(int child_id, IPC::Message* raw_message) {
85   using content::RenderProcessHost;
86   scoped_ptr<IPC::Message> own_message(raw_message);
87
88   RenderProcessHost* render_process_host = RenderProcessHost::FromID(child_id);
89   if (!render_process_host)
90     return;
91   render_process_host->Send(own_message.release());
92 }
93
94 }  // namespace
95
96 // Helper class to implement PrerenderContents::Observer and watch prerenders
97 // which launch other prerenders.
98 class PrerenderLinkManager::PendingPrerenderManager
99     : public PrerenderContents::Observer {
100  public:
101   explicit PendingPrerenderManager(PrerenderLinkManager* link_manager)
102       : link_manager_(link_manager) {}
103
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);
109     }
110   }
111
112   void ObserveLauncher(PrerenderContents* launcher) {
113     DCHECK_EQ(FINAL_STATUS_MAX, launcher->final_status());
114     if (observed_launchers_.find(launcher) != observed_launchers_.end())
115       return;
116     observed_launchers_.insert(launcher);
117     launcher->AddObserver(this);
118   }
119
120   virtual void OnPrerenderStart(PrerenderContents* launcher) OVERRIDE {}
121
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);
126     } else {
127       link_manager_->CancelPendingPrerendersForLauncher(launcher);
128     }
129   }
130
131  private:
132   // A pointer to the parent PrerenderLinkManager.
133   PrerenderLinkManager* link_manager_;
134
135   // The set of PrerenderContentses being observed. Lifetimes are managed by
136   // OnPrerenderStop.
137   std::set<PrerenderContents*> observed_launchers_;
138 };
139
140 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager)
141     : has_shutdown_(false),
142       manager_(manager),
143       pending_prerender_manager_(new PendingPrerenderManager(this)) {}
144
145 PrerenderLinkManager::~PrerenderLinkManager() {
146   for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
147        i != prerenders_.end(); ++i) {
148     if (i->handle) {
149       DCHECK(!i->handle->IsPrerendering())
150           << "All running prerenders should stop at the same time as the "
151           << "PrerenderManager.";
152       delete i->handle;
153       i->handle = 0;
154     }
155   }
156 }
157
158 void PrerenderLinkManager::OnAddPrerender(int launcher_child_id,
159                                           int prerender_id,
160                                           const GURL& url,
161                                           uint32 rel_types,
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,
167                                                 prerender_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())
173     return;
174
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());
184     return;
185   }
186
187   LinkPrerender
188       prerender(launcher_child_id, prerender_id, url, rel_types, referrer, size,
189                 render_view_route_id, manager_->GetCurrentTimeTicks(),
190                 prerender_contents);
191   prerenders_.push_back(prerender);
192   RecordLinkManagerAdded(rel_types);
193   if (prerender_contents)
194     pending_prerender_manager_->ObserveLauncher(prerender_contents);
195   else
196     StartPrerenders();
197 }
198
199 void PrerenderLinkManager::OnCancelPrerender(int child_id, int prerender_id) {
200   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
201                                                                  prerender_id);
202   if (!prerender)
203     return;
204
205   CancelPrerender(prerender);
206   StartPrerenders();
207 }
208
209 void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
210   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
211                                                                  prerender_id);
212   if (!prerender)
213     return;
214
215   if (!prerender->handle) {
216     RemovePrerender(prerender);
217     return;
218   }
219
220   prerender->has_been_abandoned = true;
221   prerender->handle->OnNavigateAway();
222   DCHECK(prerender->handle);
223
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
226   // will remove it.
227   if (!prerender->handle->IsPrerendering())
228     RemovePrerender(prerender);
229 }
230
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;
235     ++next;
236
237     if (child_id != it->launcher_child_id)
238       continue;
239
240     const size_t running_prerender_count = CountRunningPrerenders();
241     OnAbandonPrerender(child_id, it->prerender_id);
242     DCHECK_EQ(running_prerender_count, CountRunningPrerenders());
243   }
244 }
245
246 PrerenderLinkManager::LinkPrerender::LinkPrerender(
247     int launcher_child_id,
248     int prerender_id,
249     const GURL& url,
250     uint32 rel_types,
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),
258       url(url),
259       rel_types(rel_types),
260       referrer(referrer),
261       size(size),
262       render_view_route_id(render_view_route_id),
263       creation_time(creation_time),
264       deferred_launcher(deferred_launcher),
265       handle(NULL),
266       is_match_complete_replacement(false),
267       has_been_abandoned(false) {
268 }
269
270 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
271   DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle)
272       << "The PrerenderHandle should be destroyed before its Prerender.";
273 }
274
275 bool PrerenderLinkManager::IsEmpty() const {
276   return prerenders_.empty();
277 }
278
279 size_t PrerenderLinkManager::CountRunningPrerenders() const {
280   size_t retval = 0;
281   for (std::list<LinkPrerender>::const_iterator i = prerenders_.begin();
282        i != prerenders_.end(); ++i) {
283     if (i->handle && i->handle->IsPrerendering())
284       ++retval;
285   }
286   return retval;
287 }
288
289 void PrerenderLinkManager::StartPrerenders() {
290   if (has_shutdown_)
291     return;
292
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;
298
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)
306       continue;
307     if (!i->handle) {
308       pending_prerenders.push_back(i);
309     } else {
310       ++total_started_prerender_count;
311       if (i->has_been_abandoned) {
312         abandoned_prerenders.push_back(&(*i));
313       } else {
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));
323       }
324     }
325
326     DCHECK_EQ(&(*i), FindByLauncherChildIdAndPrerenderId(i->launcher_child_id,
327                                                          i->prerender_id));
328   }
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);
333
334   TimeTicks now = manager_->GetCurrentTimeTicks();
335
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();
339        i != end; ++i) {
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);
344       continue;
345     }
346
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.
353       continue;
354     }
355
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();
365       } else {
366         return;
367       }
368     }
369
370     if (!ShouldStartPrerender((*i)->rel_types)) {
371       prerenders_.erase(*i);
372       continue;
373     }
374
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);
378     if (!handle) {
379       // This prerender couldn't be launched, it's gone.
380       prerenders_.erase(*i);
381       continue;
382     }
383
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);
391
392     running_launcher_and_render_view_routes.insert(
393         launcher_and_render_view_route);
394   }
395 }
396
397 PrerenderLinkManager::LinkPrerender*
398 PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id,
399                                                           int prerender_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) {
404       return &(*i);
405     }
406   }
407   return NULL;
408 }
409
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)
416       return &(*i);
417   }
418   return NULL;
419 }
420
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);
426       i->handle = NULL;
427       prerenders_.erase(i);
428       return;
429     }
430   }
431   NOTREACHED();
432 }
433
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);
439       i->handle = NULL;
440       prerenders_.erase(i);
441       if (own_handle)
442         own_handle->OnCancel();
443       return;
444     }
445   }
446   NOTREACHED();
447 }
448
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;
455   }
456   StartPrerenders();
457 }
458
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) {
466       DCHECK(!i->handle);
467       to_erase.push_back(i);
468     }
469   }
470   std::for_each(to_erase.begin(), to_erase.end(),
471                 std::bind1st(std::mem_fun(&std::list<LinkPrerender>::erase),
472                              &prerenders_));
473 }
474
475 void PrerenderLinkManager::Shutdown() {
476   has_shutdown_ = true;
477 }
478
479 // In practice, this is always called from PrerenderLinkManager::OnAddPrerender.
480 void PrerenderLinkManager::OnPrerenderStart(
481     PrerenderHandle* prerender_handle) {
482   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
483   if (!prerender)
484     return;
485   Send(prerender->launcher_child_id,
486        new PrerenderMsg_OnPrerenderStart(prerender->prerender_id));
487 }
488
489 void PrerenderLinkManager::OnPrerenderStopLoading(
490     PrerenderHandle* prerender_handle) {
491   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
492   if (!prerender)
493     return;
494
495   Send(prerender->launcher_child_id,
496        new PrerenderMsg_OnPrerenderStopLoading(prerender->prerender_id));
497 }
498
499 void PrerenderLinkManager::OnPrerenderDomContentLoaded(
500     PrerenderHandle* prerender_handle) {
501   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
502   if (!prerender)
503     return;
504
505   Send(prerender->launcher_child_id,
506        new PrerenderMsg_OnPrerenderDomContentLoaded(prerender->prerender_id));
507 }
508
509 void PrerenderLinkManager::OnPrerenderStop(
510     PrerenderHandle* prerender_handle) {
511   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
512   if (!prerender)
513     return;
514
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));
520   }
521   RemovePrerender(prerender);
522   StartPrerenders();
523 }
524
525 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
526     PrerenderHandle* prerender_handle) {
527   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
528   if (!prerender)
529     return;
530
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
537   // cancelations.
538 }
539
540 }  // namespace prerender