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 <limits>
8#include <set>
9#include <utility>
10
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"
23#include "url/gurl.h"
24
25using base::TimeDelta;
26using base::TimeTicks;
27using content::RenderViewHost;
28using content::SessionStorageNamespace;
29
30namespace {
31
32void Send(int child_id, IPC::Message* raw_message) {
33  using content::RenderProcessHost;
34  scoped_ptr<IPC::Message> own_message(raw_message);
35
36  RenderProcessHost* render_process_host = RenderProcessHost::FromID(child_id);
37  if (!render_process_host)
38    return;
39  render_process_host->Send(own_message.release());
40}
41
42}  // namespace
43
44namespace prerender {
45
46PrerenderLinkManager::PrerenderLinkManager(PrerenderManager* manager)
47    : has_shutdown_(false),
48      manager_(manager) {
49}
50
51PrerenderLinkManager::~PrerenderLinkManager() {
52  for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
53       i != prerenders_.end(); ++i) {
54    if (i->handle) {
55      DCHECK(!i->handle->IsPrerendering())
56          << "All running prerenders should stop at the same time as the "
57          << "PrerenderManager.";
58      delete i->handle;
59      i->handle = 0;
60    }
61  }
62}
63
64void PrerenderLinkManager::OnAddPrerender(int launcher_child_id,
65                                          int prerender_id,
66                                          const GURL& url,
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,
72                                                prerender_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())
78    return;
79
80  LinkPrerender
81      prerender(launcher_child_id, prerender_id, url, referrer, size,
82                render_view_route_id, manager_->GetCurrentTimeTicks());
83  prerenders_.push_back(prerender);
84  StartPrerenders();
85}
86
87void PrerenderLinkManager::OnCancelPrerender(int child_id, int prerender_id) {
88  LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
89                                                                 prerender_id);
90  if (!prerender)
91    return;
92
93  // Remove the handle from the PrerenderLinkManager before we cancel this
94  // prerender, to avoid reentering the PrerenderLinkManager, sending events to
95  // the underlying prerender and making a second erase.
96  scoped_ptr<PrerenderHandle> own_prerender_handle(prerender->handle);
97  prerender->handle = NULL;
98  RemovePrerender(prerender);
99
100  if (own_prerender_handle)
101    own_prerender_handle->OnCancel();
102
103  StartPrerenders();
104}
105
106void PrerenderLinkManager::OnAbandonPrerender(int child_id, int prerender_id) {
107  LinkPrerender* prerender = FindByLauncherChildIdAndPrerenderId(child_id,
108                                                                 prerender_id);
109  if (!prerender)
110    return;
111
112  if (!prerender->handle) {
113    RemovePrerender(prerender);
114    return;
115  }
116
117  prerender->handle->OnNavigateAway();
118  DCHECK(prerender->handle);
119
120  // If the prerender is not running, remove it from the list so it does not
121  // leak. If it is running, it will send a cancel event when it stops which
122  // will remove it.
123  if (!prerender->handle->IsPrerendering())
124    RemovePrerender(prerender);
125}
126
127void PrerenderLinkManager::OnChannelClosing(int child_id) {
128  std::list<LinkPrerender>::iterator next = prerenders_.begin();
129  while (next != prerenders_.end()) {
130    std::list<LinkPrerender>::iterator it = next;
131    ++next;
132
133    if (child_id != it->launcher_child_id)
134      continue;
135
136    const size_t running_prerender_count = CountRunningPrerenders();
137    OnAbandonPrerender(child_id, it->prerender_id);
138    DCHECK_EQ(running_prerender_count, CountRunningPrerenders());
139  }
140}
141
142PrerenderLinkManager::LinkPrerender::LinkPrerender(
143    int launcher_child_id,
144    int prerender_id,
145    const GURL& url,
146    const content::Referrer& referrer,
147    const gfx::Size& size,
148    int render_view_route_id,
149    TimeTicks creation_time) : launcher_child_id(launcher_child_id),
150                               prerender_id(prerender_id),
151                               url(url),
152                               referrer(referrer),
153                               size(size),
154                               render_view_route_id(render_view_route_id),
155                               creation_time(creation_time),
156                               handle(NULL) {
157}
158
159PrerenderLinkManager::LinkPrerender::~LinkPrerender() {
160  DCHECK_EQ(static_cast<PrerenderHandle*>(NULL), handle)
161      << "The PrerenderHandle should be destroyed before its Prerender.";
162}
163
164bool PrerenderLinkManager::IsEmpty() const {
165  return prerenders_.empty();
166}
167
168size_t PrerenderLinkManager::CountRunningPrerenders() const {
169  size_t retval = 0;
170  for (std::list<LinkPrerender>::const_iterator i = prerenders_.begin();
171       i != prerenders_.end(); ++i) {
172    if (i->handle && i->handle->IsPrerendering())
173      ++retval;
174  }
175  return retval;
176}
177
178void PrerenderLinkManager::StartPrerenders() {
179  if (has_shutdown_)
180    return;
181
182  size_t total_started_prerender_count = 0;
183  std::multiset<std::pair<int, int> >
184      running_launcher_and_render_view_routes;
185
186  // Scan the list, counting how many prerenders have handles (and so were added
187  // to the PrerenderManager). The count is done for the system as a whole, and
188  // also per launcher.
189  for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
190       i != prerenders_.end(); ++i) {
191    if (i->handle) {
192      ++total_started_prerender_count;
193      std::pair<int, int> launcher_and_render_view_route(
194          i->launcher_child_id, i->render_view_route_id);
195      running_launcher_and_render_view_routes.insert(
196          launcher_and_render_view_route);
197      DCHECK_GE(manager_->config().max_link_concurrency_per_launcher,
198                running_launcher_and_render_view_routes.count(
199                    launcher_and_render_view_route));
200    }
201
202    DCHECK_EQ(&(*i), FindByLauncherChildIdAndPrerenderId(i->launcher_child_id,
203                                                         i->prerender_id));
204  }
205  DCHECK_GE(manager_->config().max_link_concurrency,
206            total_started_prerender_count);
207  DCHECK_LE(CountRunningPrerenders(), total_started_prerender_count);
208
209  TimeTicks now = manager_->GetCurrentTimeTicks();
210
211  // Scan the list again, starting prerenders as our counts allow.
212  std::list<LinkPrerender>::iterator next = prerenders_.begin();
213  while (next != prerenders_.end()) {
214    std::list<LinkPrerender>::iterator i = next;
215    ++next;
216
217    if (total_started_prerender_count >=
218            manager_->config().max_link_concurrency ||
219        total_started_prerender_count >= prerenders_.size()) {
220      // The system is already at its prerender concurrency limit.
221      return;
222    }
223
224    if (i->handle) {
225      // This prerender has already been added to the prerender manager.
226      continue;
227    }
228
229    TimeDelta prerender_age = now - i->creation_time;
230    if (prerender_age >= manager_->config().max_wait_to_launch) {
231      // This prerender waited too long in the queue before launching.
232      prerenders_.erase(i);
233      continue;
234    }
235
236    std::pair<int, int> launcher_and_render_view_route(
237        i->launcher_child_id, i->render_view_route_id);
238    if (manager_->config().max_link_concurrency_per_launcher <=
239        running_launcher_and_render_view_routes.count(
240            launcher_and_render_view_route)) {
241      // This prerender's launcher is already at its limit.
242      continue;
243    }
244
245    PrerenderHandle* handle = manager_->AddPrerenderFromLinkRelPrerender(
246        i->launcher_child_id, i->render_view_route_id,
247        i->url, i->referrer, i->size);
248    if (!handle) {
249      // This prerender couldn't be launched, it's gone.
250      prerenders_.erase(i);
251      continue;
252    }
253
254    // We have successfully started a new prerender.
255    i->handle = handle;
256    ++total_started_prerender_count;
257    handle->SetObserver(this);
258    if (handle->IsPrerendering())
259      OnPrerenderStart(handle);
260
261    running_launcher_and_render_view_routes.insert(
262        launcher_and_render_view_route);
263  }
264}
265
266PrerenderLinkManager::LinkPrerender*
267PrerenderLinkManager::FindByLauncherChildIdAndPrerenderId(int launcher_child_id,
268                                                          int prerender_id) {
269  for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
270       i != prerenders_.end(); ++i) {
271    if (launcher_child_id == i->launcher_child_id &&
272        prerender_id == i->prerender_id) {
273      return &(*i);
274    }
275  }
276  return NULL;
277}
278
279PrerenderLinkManager::LinkPrerender*
280PrerenderLinkManager::FindByPrerenderHandle(PrerenderHandle* prerender_handle) {
281  DCHECK(prerender_handle);
282  for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
283       i != prerenders_.end(); ++i) {
284    if (prerender_handle == i->handle)
285      return &(*i);
286  }
287  return NULL;
288}
289
290void PrerenderLinkManager::RemovePrerender(LinkPrerender* prerender) {
291  for (std::list<LinkPrerender>::iterator i = prerenders_.begin();
292       i != prerenders_.end(); ++i) {
293    if (&(*i) == prerender) {
294      scoped_ptr<PrerenderHandle> own_handle(i->handle);
295      i->handle = NULL;
296      prerenders_.erase(i);
297      return;
298    }
299  }
300  NOTREACHED();
301}
302
303void PrerenderLinkManager::Shutdown() {
304  has_shutdown_ = true;
305}
306
307// In practice, this is always called from either
308// PrerenderLinkManager::OnAddPrerender in the regular case, or in the pending
309// prerender case, from PrerenderHandle::AdoptPrerenderDataFrom.
310void PrerenderLinkManager::OnPrerenderStart(
311    PrerenderHandle* prerender_handle) {
312  LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
313  if (!prerender)
314    return;
315  Send(prerender->launcher_child_id,
316       new PrerenderMsg_OnPrerenderStart(prerender->prerender_id));
317}
318
319void PrerenderLinkManager::OnPrerenderStopLoading(
320    PrerenderHandle* prerender_handle) {
321  LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
322  if (!prerender)
323    return;
324
325  Send(prerender->launcher_child_id,
326       new PrerenderMsg_OnPrerenderStopLoading(prerender->prerender_id));
327}
328
329void PrerenderLinkManager::OnPrerenderStop(
330    PrerenderHandle* prerender_handle) {
331  LinkPrerender* prerender = FindByPrerenderHandle(prerender_handle);
332  if (!prerender)
333    return;
334
335  Send(prerender->launcher_child_id,
336       new PrerenderMsg_OnPrerenderStop(prerender->prerender_id));
337  RemovePrerender(prerender);
338  StartPrerenders();
339}
340
341}  // namespace prerender
342