1// Copyright (c) 2011 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_contents.h"
6
7#include "base/process_util.h"
8#include "base/task.h"
9#include "base/utf_string_conversions.h"
10#include "chrome/browser/background_contents_service.h"
11#include "chrome/browser/browser_process.h"
12#include "chrome/browser/prerender/prerender_final_status.h"
13#include "chrome/browser/prerender/prerender_manager.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/renderer_preferences_util.h"
16#include "chrome/browser/ui/login/login_prompt.h"
17#include "chrome/common/extensions/extension_constants.h"
18#include "chrome/common/icon_messages.h"
19#include "chrome/common/render_messages.h"
20#include "chrome/common/extensions/extension_messages.h"
21#include "chrome/common/url_constants.h"
22#include "chrome/common/view_types.h"
23#include "content/browser/browsing_instance.h"
24#include "content/browser/renderer_host/render_view_host.h"
25#include "content/browser/renderer_host/resource_dispatcher_host.h"
26#include "content/browser/renderer_host/resource_request_details.h"
27#include "content/browser/site_instance.h"
28#include "content/common/notification_service.h"
29#include "content/common/view_messages.h"
30#include "ui/gfx/rect.h"
31
32#if defined(OS_MACOSX)
33#include "chrome/browser/mach_broker_mac.h"
34#endif
35
36namespace prerender {
37
38void AddChildRoutePair(ResourceDispatcherHost* rdh,
39                       int child_id, int route_id) {
40  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
41  rdh->AddPrerenderChildRoutePair(child_id, route_id);
42}
43
44void RemoveChildRoutePair(ResourceDispatcherHost* rdh,
45                          int child_id, int route_id) {
46  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
47  rdh->RemovePrerenderChildRoutePair(child_id, route_id);
48}
49
50class PrerenderContentsFactoryImpl : public PrerenderContents::Factory {
51 public:
52  virtual PrerenderContents* CreatePrerenderContents(
53      PrerenderManager* prerender_manager, Profile* profile, const GURL& url,
54      const std::vector<GURL>& alias_urls, const GURL& referrer) {
55    return new PrerenderContents(prerender_manager, profile, url, alias_urls,
56                                 referrer);
57  }
58};
59
60PrerenderContents::PrerenderContents(PrerenderManager* prerender_manager,
61                                     Profile* profile,
62                                     const GURL& url,
63                                     const std::vector<GURL>& alias_urls,
64                                     const GURL& referrer)
65    : prerender_manager_(prerender_manager),
66      render_view_host_(NULL),
67      prerender_url_(url),
68      referrer_(referrer),
69      profile_(profile),
70      page_id_(0),
71      has_stopped_loading_(false),
72      final_status_(FINAL_STATUS_MAX),
73      prerendering_has_started_(false) {
74  DCHECK(prerender_manager != NULL);
75  if (!AddAliasURL(prerender_url_))
76    LOG(DFATAL) << "PrerenderContents given invalid URL " << prerender_url_;
77  for (std::vector<GURL>::const_iterator it = alias_urls.begin();
78       it != alias_urls.end();
79       ++it) {
80    if (!AddAliasURL(*it))
81      LOG(DFATAL) << "PrerenderContents given invalid URL " << prerender_url_;
82  }
83}
84
85// static
86PrerenderContents::Factory* PrerenderContents::CreateFactory() {
87  return new PrerenderContentsFactoryImpl();
88}
89
90void PrerenderContents::StartPrerendering() {
91  DCHECK(profile_ != NULL);
92  DCHECK(!prerendering_has_started_);
93  prerendering_has_started_ = true;
94  SiteInstance* site_instance = SiteInstance::CreateSiteInstance(profile_);
95  render_view_host_ = new RenderViewHost(site_instance, this, MSG_ROUTING_NONE,
96                                         NULL);
97
98  int process_id = render_view_host_->process()->id();
99  int view_id = render_view_host_->routing_id();
100  std::pair<int, int> process_view_pair = std::make_pair(process_id, view_id);
101  NotificationService::current()->Notify(
102      NotificationType::PRERENDER_CONTENTS_STARTED,
103      Source<std::pair<int, int> >(&process_view_pair),
104      NotificationService::NoDetails());
105
106  // Create the RenderView, so it can receive messages.
107  render_view_host_->CreateRenderView(string16());
108
109  // Hide the RVH, so that we will run at a lower CPU priority.
110  // Once the RVH is being swapped into a tab, we will Restore it again.
111  render_view_host_->WasHidden();
112
113  // Register this with the ResourceDispatcherHost as a prerender
114  // RenderViewHost. This must be done before the Navigate message to catch all
115  // resource requests, but as it is on the same thread as the Navigate message
116  // (IO) there is no race condition.
117  ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
118  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
119                          NewRunnableFunction(&AddChildRoutePair, rdh,
120                                              process_id, view_id));
121
122
123  // Close ourselves when the application is shutting down.
124  registrar_.Add(this, NotificationType::APP_TERMINATING,
125                 NotificationService::AllSources());
126
127  // Register for our parent profile to shutdown, so we can shut ourselves down
128  // as well (should only be called for OTR profiles, as we should receive
129  // APP_TERMINATING before non-OTR profiles are destroyed).
130  // TODO(tburkard): figure out if this is needed.
131  registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
132                 Source<Profile>(profile_));
133
134  // Register to cancel if Authentication is required.
135  registrar_.Add(this, NotificationType::AUTH_NEEDED,
136                 NotificationService::AllSources());
137
138  registrar_.Add(this, NotificationType::AUTH_CANCELLED,
139                 NotificationService::AllSources());
140
141  // Register all responses to see if we should cancel.
142  registrar_.Add(this, NotificationType::DOWNLOAD_INITIATED,
143                 NotificationService::AllSources());
144
145  // Register for redirect notifications sourced from |this|.
146  registrar_.Add(this, NotificationType::RESOURCE_RECEIVED_REDIRECT,
147                 Source<RenderViewHostDelegate>(this));
148
149  DCHECK(load_start_time_.is_null());
150  load_start_time_ = base::TimeTicks::Now();
151
152  ViewMsg_Navigate_Params params;
153  params.page_id = -1;
154  params.pending_history_list_offset = -1;
155  params.current_history_list_offset = -1;
156  params.current_history_list_length = 0;
157  params.url = prerender_url_;
158  params.transition = PageTransition::LINK;
159  params.navigation_type = ViewMsg_Navigate_Type::PRERENDER;
160  params.referrer = referrer_;
161
162  render_view_host_->Navigate(params);
163}
164
165bool PrerenderContents::GetChildId(int* child_id) const {
166  CHECK(child_id);
167  if (render_view_host_) {
168    *child_id = render_view_host_->process()->id();
169    return true;
170  }
171  return false;
172}
173
174bool PrerenderContents::GetRouteId(int* route_id) const {
175  CHECK(route_id);
176  if (render_view_host_) {
177    *route_id = render_view_host_->routing_id();
178    return true;
179  }
180  return false;
181}
182
183void PrerenderContents::set_final_status(FinalStatus final_status) {
184  DCHECK(final_status >= FINAL_STATUS_USED && final_status < FINAL_STATUS_MAX);
185  DCHECK_EQ(FINAL_STATUS_MAX, final_status_);
186
187  final_status_ = final_status;
188}
189
190FinalStatus PrerenderContents::final_status() const {
191  return final_status_;
192}
193
194PrerenderContents::~PrerenderContents() {
195  DCHECK(final_status_ != FINAL_STATUS_MAX);
196
197  // If we haven't even started prerendering, we were just in the control
198  // group, which means we do not want to record the status.
199  if (prerendering_has_started())
200    RecordFinalStatus(final_status_);
201
202  if (!render_view_host_)   // Will be null for unit tests.
203    return;
204
205  int process_id = render_view_host_->process()->id();
206  int view_id = render_view_host_->routing_id();
207  std::pair<int, int> process_view_pair = std::make_pair(process_id, view_id);
208  NotificationService::current()->Notify(
209      NotificationType::PRERENDER_CONTENTS_DESTROYED,
210      Source<std::pair<int, int> >(&process_view_pair),
211      NotificationService::NoDetails());
212
213  ResourceDispatcherHost* rdh = g_browser_process->resource_dispatcher_host();
214  BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
215                          NewRunnableFunction(&RemoveChildRoutePair, rdh,
216                                              process_id, view_id));
217  render_view_host_->Shutdown();  // deletes render_view_host
218}
219
220RenderViewHostDelegate::View* PrerenderContents::GetViewDelegate() {
221  return this;
222}
223
224const GURL& PrerenderContents::GetURL() const {
225  return url_;
226}
227
228ViewType::Type PrerenderContents::GetRenderViewType() const {
229  return ViewType::BACKGROUND_CONTENTS;
230}
231
232int PrerenderContents::GetBrowserWindowID() const {
233  return extension_misc::kUnknownWindowId;
234}
235
236void PrerenderContents::DidNavigate(
237    RenderViewHost* render_view_host,
238    const ViewHostMsg_FrameNavigate_Params& params) {
239  // We only care when the outer frame changes.
240  if (!PageTransition::IsMainFrame(params.transition))
241    return;
242
243  // Store the navigation params.
244  ViewHostMsg_FrameNavigate_Params* p = new ViewHostMsg_FrameNavigate_Params();
245  *p = params;
246  navigate_params_.reset(p);
247
248  if (!AddAliasURL(params.url)) {
249    Destroy(FINAL_STATUS_HTTPS);
250    return;
251  }
252
253  url_ = params.url;
254}
255
256void PrerenderContents::UpdateTitle(RenderViewHost* render_view_host,
257                                    int32 page_id,
258                                    const std::wstring& title) {
259  if (title.empty()) {
260    return;
261  }
262
263  title_ = WideToUTF16Hack(title);
264  page_id_ = page_id;
265}
266
267void PrerenderContents::RunJavaScriptMessage(
268    const std::wstring& message,
269    const std::wstring& default_prompt,
270    const GURL& frame_url,
271    const int flags,
272    IPC::Message* reply_msg,
273    bool* did_suppress_message) {
274  // Always suppress JavaScript messages if they're triggered by a page being
275  // prerendered.
276  *did_suppress_message = true;
277  // We still want to show the user the message when they navigate to this
278  // page, so cancel this prerender.
279  Destroy(FINAL_STATUS_JAVASCRIPT_ALERT);
280}
281
282bool PrerenderContents::PreHandleKeyboardEvent(
283    const NativeWebKeyboardEvent& event,
284    bool* is_keyboard_shortcut) {
285  return false;
286}
287
288void PrerenderContents::Observe(NotificationType type,
289                                const NotificationSource& source,
290                                const NotificationDetails& details) {
291  switch (type.value) {
292    case NotificationType::PROFILE_DESTROYED:
293      Destroy(FINAL_STATUS_PROFILE_DESTROYED);
294      return;
295
296    case NotificationType::APP_TERMINATING:
297      Destroy(FINAL_STATUS_APP_TERMINATING);
298      return;
299
300    case NotificationType::AUTH_NEEDED:
301    case NotificationType::AUTH_CANCELLED: {
302      // Prerendered pages have a NULL controller and the login handler should
303      // be referencing us as the render view host delegate.
304      NavigationController* controller =
305          Source<NavigationController>(source).ptr();
306      LoginNotificationDetails* details_ptr =
307          Details<LoginNotificationDetails>(details).ptr();
308      LoginHandler* handler = details_ptr->handler();
309      DCHECK(handler != NULL);
310      RenderViewHostDelegate* delegate = handler->GetRenderViewHostDelegate();
311      if (controller == NULL && delegate == this) {
312        Destroy(FINAL_STATUS_AUTH_NEEDED);
313        return;
314      }
315      break;
316    }
317
318    case NotificationType::DOWNLOAD_INITIATED: {
319      // If the download is started from a RenderViewHost that we are
320      // delegating, kill the prerender. This cancels any pending requests
321      // though the download never actually started thanks to the
322      // DownloadRequestLimiter.
323      DCHECK(NotificationService::NoDetails() == details);
324      RenderViewHost* rvh = Source<RenderViewHost>(source).ptr();
325      CHECK(rvh != NULL);
326      if (rvh->delegate() == this) {
327        Destroy(FINAL_STATUS_DOWNLOAD);
328        return;
329      }
330      break;
331    }
332
333    case NotificationType::RESOURCE_RECEIVED_REDIRECT: {
334      // RESOURCE_RECEIVED_REDIRECT can come for any resource on a page.
335      // If it's a redirect on the top-level resource, the name needs
336      // to be remembered for future matching, and if it redirects to
337      // an https resource, it needs to be canceled. If a subresource
338      // is redirected, nothing changes.
339      DCHECK(Source<RenderViewHostDelegate>(source).ptr() == this);
340      ResourceRedirectDetails* resource_redirect_details =
341          Details<ResourceRedirectDetails>(details).ptr();
342      CHECK(resource_redirect_details);
343      if (resource_redirect_details->resource_type() ==
344          ResourceType::MAIN_FRAME) {
345        if (!AddAliasURL(resource_redirect_details->new_url()))
346          Destroy(FINAL_STATUS_HTTPS);
347      }
348      break;
349    }
350
351    default:
352      NOTREACHED() << "Unexpected notification sent.";
353      break;
354  }
355}
356
357void PrerenderContents::OnMessageBoxClosed(IPC::Message* reply_msg,
358                                           bool success,
359                                           const std::wstring& prompt) {
360  render_view_host_->JavaScriptMessageBoxClosed(reply_msg, success, prompt);
361}
362
363gfx::NativeWindow PrerenderContents::GetMessageBoxRootWindow() {
364  NOTIMPLEMENTED();
365  return NULL;
366}
367
368TabContents* PrerenderContents::AsTabContents() {
369  return NULL;
370}
371
372ExtensionHost* PrerenderContents::AsExtensionHost() {
373  return NULL;
374}
375
376void PrerenderContents::UpdateInspectorSetting(const std::string& key,
377                                               const std::string& value) {
378  RenderViewHostDelegateHelper::UpdateInspectorSetting(profile_, key, value);
379}
380
381void PrerenderContents::ClearInspectorSettings() {
382  RenderViewHostDelegateHelper::ClearInspectorSettings(profile_);
383}
384
385void PrerenderContents::Close(RenderViewHost* render_view_host) {
386  Destroy(FINAL_STATUS_CLOSED);
387}
388
389RendererPreferences PrerenderContents::GetRendererPrefs(
390    Profile* profile) const {
391  RendererPreferences preferences;
392  renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile);
393  return preferences;
394}
395
396WebPreferences PrerenderContents::GetWebkitPrefs() {
397  return RenderViewHostDelegateHelper::GetWebkitPrefs(profile_,
398                                                      false);  // is_web_ui
399}
400
401void PrerenderContents::CreateNewWindow(
402    int route_id,
403    const ViewHostMsg_CreateWindow_Params& params) {
404  // Since we don't want to permit child windows that would have a
405  // window.opener property, terminate prerendering.
406  Destroy(FINAL_STATUS_CREATE_NEW_WINDOW);
407}
408
409void PrerenderContents::CreateNewWidget(int route_id,
410                                        WebKit::WebPopupType popup_type) {
411  NOTREACHED();
412}
413
414void PrerenderContents::CreateNewFullscreenWidget(int route_id) {
415  NOTREACHED();
416}
417
418void PrerenderContents::ShowCreatedWindow(int route_id,
419                                          WindowOpenDisposition disposition,
420                                          const gfx::Rect& initial_pos,
421                                          bool user_gesture) {
422  // TODO(tburkard): need to figure out what the correct behavior here is
423  NOTIMPLEMENTED();
424}
425
426void PrerenderContents::ShowCreatedWidget(int route_id,
427                                          const gfx::Rect& initial_pos) {
428  NOTIMPLEMENTED();
429}
430
431void PrerenderContents::ShowCreatedFullscreenWidget(int route_id) {
432  NOTIMPLEMENTED();
433}
434
435bool PrerenderContents::OnMessageReceived(const IPC::Message& message) {
436  bool handled = true;
437  bool message_is_ok = true;
438  IPC_BEGIN_MESSAGE_MAP_EX(PrerenderContents, message, message_is_ok)
439    IPC_MESSAGE_HANDLER(ViewHostMsg_DidStartProvisionalLoadForFrame,
440                        OnDidStartProvisionalLoadForFrame)
441    IPC_MESSAGE_HANDLER(IconHostMsg_UpdateFaviconURL, OnUpdateFaviconURL)
442    IPC_MESSAGE_HANDLER(ViewHostMsg_MaybeCancelPrerenderForHTML5Media,
443                        OnMaybeCancelPrerenderForHTML5Media)
444    IPC_MESSAGE_UNHANDLED(handled = false)
445  IPC_END_MESSAGE_MAP_EX()
446
447  return handled;
448}
449
450void PrerenderContents::OnDidStartProvisionalLoadForFrame(int64 frame_id,
451                                                          bool is_main_frame,
452                                                          const GURL& url) {
453  if (is_main_frame) {
454    if (!AddAliasURL(url)) {
455      Destroy(FINAL_STATUS_HTTPS);
456      return;
457    }
458
459    // Usually, this event fires if the user clicks or enters a new URL.
460    // Neither of these can happen in the case of an invisible prerender.
461    // So the cause is: Some JavaScript caused a new URL to be loaded.  In that
462    // case, the spinner would start again in the browser, so we must reset
463    // has_stopped_loading_ so that the spinner won't be stopped.
464    has_stopped_loading_ = false;
465  }
466}
467
468void PrerenderContents::OnUpdateFaviconURL(
469    int32 page_id,
470    const std::vector<FaviconURL>& urls) {
471  LOG(INFO) << "PrerenderContents::OnUpdateFaviconURL" << icon_url_;
472  for (std::vector<FaviconURL>::const_iterator i = urls.begin();
473       i != urls.end(); ++i) {
474    if (i->icon_type == FaviconURL::FAVICON) {
475      icon_url_ = i->icon_url;
476      LOG(INFO) << icon_url_;
477      return;
478    }
479  }
480}
481
482void PrerenderContents::OnMaybeCancelPrerenderForHTML5Media() {
483  Destroy(FINAL_STATUS_HTML5_MEDIA);
484}
485
486bool PrerenderContents::AddAliasURL(const GURL& url) {
487  if (!url.SchemeIs("http"))
488    return false;
489  alias_urls_.push_back(url);
490  return true;
491}
492
493bool PrerenderContents::MatchesURL(const GURL& url) const {
494  return std::find(alias_urls_.begin(), alias_urls_.end(), url)
495      != alias_urls_.end();
496}
497
498void PrerenderContents::DidStopLoading() {
499  has_stopped_loading_ = true;
500}
501
502void PrerenderContents::Destroy(FinalStatus final_status) {
503  prerender_manager_->RemoveEntry(this);
504  set_final_status(final_status);
505  delete this;
506}
507
508void PrerenderContents::OnJSOutOfMemory() {
509  Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY);
510}
511
512void PrerenderContents::RendererUnresponsive(RenderViewHost* render_view_host,
513                                             bool is_during_unload) {
514  Destroy(FINAL_STATUS_RENDERER_UNRESPONSIVE);
515}
516
517
518base::ProcessMetrics* PrerenderContents::MaybeGetProcessMetrics() {
519  if (process_metrics_.get() == NULL) {
520    // If a PrenderContents hasn't started prerending, don't be fully formed.
521    if (!render_view_host_ || !render_view_host_->process())
522      return NULL;
523    base::ProcessHandle handle = render_view_host_->process()->GetHandle();
524    if (handle == base::kNullProcessHandle)
525      return NULL;
526#if !defined(OS_MACOSX)
527    process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(handle));
528#else
529    process_metrics_.reset(base::ProcessMetrics::CreateProcessMetrics(
530        handle,
531        MachBroker::GetInstance()));
532#endif
533  }
534
535  return process_metrics_.get();
536}
537
538void PrerenderContents::DestroyWhenUsingTooManyResources() {
539  base::ProcessMetrics* metrics = MaybeGetProcessMetrics();
540  if (metrics == NULL)
541    return;
542
543  size_t private_bytes, shared_bytes;
544  if (metrics->GetMemoryBytes(&private_bytes, &shared_bytes)) {
545    if (private_bytes > kMaxPrerenderPrivateMB * 1024 * 1024)
546      Destroy(FINAL_STATUS_MEMORY_LIMIT_EXCEEDED);
547  }
548}
549
550}  // namespace prerender
551