Update To 11.40.268.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 #if defined(ENABLE_EXTENSIONS)
31 #include "extensions/browser/guest_view/guest_view_base.h"
32 #endif
33
34 using base::TimeDelta;
35 using base::TimeTicks;
36 using content::RenderViewHost;
37 using content::SessionStorageNamespace;
38
39 namespace prerender {
40
41 namespace {
42
43 bool ShouldStartRelNextPrerenders() {
44   const std::string experiment_name =
45       base::FieldTrialList::FindFullName("PrerenderRelNextTrial");
46
47   return experiment_name.find("Yes") != std::string::npos;
48 }
49
50 bool ShouldStartPrerender(const uint32 rel_types) {
51   const bool should_start_rel_next_prerenders =
52       ShouldStartRelNextPrerenders();
53
54   if (rel_types & PrerenderRelTypePrerender) {
55     return true;
56   } else if (should_start_rel_next_prerenders &&
57              (rel_types & PrerenderRelTypeNext) == PrerenderRelTypeNext) {
58     return true;
59   }
60   return false;
61 }
62
63 COMPILE_ASSERT(PrerenderRelTypePrerender == 0x1,
64                RelTypeHistogramEnum_must_match_PrerenderRelType);
65 COMPILE_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,
74 };
75
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);
80 }
81
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);
86 }
87
88 void Send(int child_id, IPC::Message* raw_message) {
89   using content::RenderProcessHost;
90   scoped_ptr<IPC::Message> own_message(raw_message);
91
92   RenderProcessHost* render_process_host = RenderProcessHost::FromID(child_id);
93   if (!render_process_host)
94     return;
95   render_process_host->Send(own_message.release());
96 }
97
98 }  // namespace
99
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) {}
107
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);
113     }
114   }
115
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);
122   }
123
124   void OnPrerenderStart(PrerenderContents* launcher) override {}
125
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);
132     }
133   }
134
135  private:
136   // A pointer to the parent PrerenderLinkManager.
137   PrerenderLinkManager* link_manager_;
138
139   // The set of PrerenderContentses being observed. Lifetimes are managed by
140   // OnPrerenderStop.
141   std::set<PrerenderContents*> observed_launchers_;
142 };
143
144 PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager)
145     : has_shutdown_(false),
146       manager_(manager),
147       pending_prerender_manager_(new PendingPrerenderManager(this)) {}
148
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;
158     }
159   }
160 }
161
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));
172
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 (extensions::GuestViewBase::IsGuest(web_contents))
181     return;
182 #endif
183
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;
194   }
195
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();
206 }
207
208 void PrerenderLinkManager::OnCancelPrerender(int child_id, int prerender_id) {
209   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
210                                                                  prerender_id);
211   if (!prerender)
212     return;
213
214   CancelPrerender(prerender);
215   StartPrerenders();
216 }
217
218 void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
219   LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
220                                                                  prerender_id);
221   if (!prerender)
222     return;
223
224   if (!prerender->handle) {
225     RemovePrerender(prerender);
226     return;
227   }
228
229   prerender->has_been_abandoned = true;
230   prerender->handle->OnNavigateAway();
231   DCHECK(prerender->handle);
232
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);
238 }
239
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;
245
246     if (child_id != it->launcher_child_id)
247       continue;
248
249     const size_t running_prerender_count = CountRunningPrerenders();
250     OnAbandonPrerender(child_id, it->prerender_id);
251     DCHECK_EQ(running_prerender_count, CountRunningPrerenders());
252   }
253 }
254
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) {
277 }
278
279 PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
280   DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle)
281       << "The PrerenderHandle should be destroyed before its Prerender.";
282 }
283
284 bool PrerenderLinkManager::IsEmpty() const {
285   return prerenders_.empty();
286 }
287
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;
294   }
295   return retval;
296 }
297
298 void PrerenderLinkManager::StartPrerenders() {
299   if (has_shutdown_)
300     return;
301
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;
307
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));
332       }
333     }
334
335     DCHECK_EQ(&(*i), FindByLauncherChildIdAndPrerenderId(i->launcher_child_id,
336                                                          i->prerender_id));
337   }
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);
342
343   TimeTicks now = manager_->GetCurrentTimeTicks();
344
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;
354     }
355
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;
363     }
364
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;
376       }
377     }
378
379     if (!ShouldStartPrerender((*i)->rel_types)) {
380       prerenders_.erase(*i);
381       continue;
382     }
383
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;
391     }
392
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);
400
401     running_launcher_and_render_view_routes.insert(
402         launcher_and_render_view_route);
403   }
404 }
405
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);
414     }
415   }
416   return NULL;
417 }
418
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);
426   }
427   return NULL;
428 }
429
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;
438     }
439   }
440   NOTREACHED();
441 }
442
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;
453     }
454   }
455   NOTREACHED();
456 }
457
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;
464   }
465   StartPrerenders();
466 }
467
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);
477     }
478   }
479   std::for_each(to_erase.begin(), to_erase.end(),
480                 std::bind1st(std::mem_fun(&std::list<LinkPrerender>::erase),
481                              &prerenders_));
482 }
483
484 void PrerenderLinkManager::Shutdown() {
485   has_shutdown_ = true;
486 }
487
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));
496 }
497
498 void PrerenderLinkManager::OnPrerenderStopLoading(
499     PrerenderHandle* prerender_handle) {
500   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
501   if (!prerender)
502     return;
503
504   Send(prerender->launcher_child_id,
505        new PrerenderMsg_OnPrerenderStopLoading(prerender->prerender_id));
506 }
507
508 void PrerenderLinkManager::OnPrerenderDomContentLoaded(
509     PrerenderHandle* prerender_handle) {
510   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
511   if (!prerender)
512     return;
513
514   Send(prerender->launcher_child_id,
515        new PrerenderMsg_OnPrerenderDomContentLoaded(prerender->prerender_id));
516 }
517
518 void PrerenderLinkManager::OnPrerenderStop(
519     PrerenderHandle* prerender_handle) {
520   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
521   if (!prerender)
522     return;
523
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));
529   }
530   RemovePrerender(prerender);
531   StartPrerenders();
532 }
533
534 void PrerenderLinkManager::OnPrerenderCreatedMatchCompleteReplacement(
535     PrerenderHandle* prerender_handle) {
536   LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
537   if (!prerender)
538     return;
539
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.
547 }
548
549 }  // namespace prerender