Home | History | Annotate | Download | only in prerender
      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 
     36 namespace prerender {
     37 
     38 void 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 
     44 void 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 
     50 class 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 
     60 PrerenderContents::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
     86 PrerenderContents::Factory* PrerenderContents::CreateFactory() {
     87   return new PrerenderContentsFactoryImpl();
     88 }
     89 
     90 void 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 
    165 bool 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 
    174 bool 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 
    183 void 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 
    190 FinalStatus PrerenderContents::final_status() const {
    191   return final_status_;
    192 }
    193 
    194 PrerenderContents::~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 
    220 RenderViewHostDelegate::View* PrerenderContents::GetViewDelegate() {
    221   return this;
    222 }
    223 
    224 const GURL& PrerenderContents::GetURL() const {
    225   return url_;
    226 }
    227 
    228 ViewType::Type PrerenderContents::GetRenderViewType() const {
    229   return ViewType::BACKGROUND_CONTENTS;
    230 }
    231 
    232 int PrerenderContents::GetBrowserWindowID() const {
    233   return extension_misc::kUnknownWindowId;
    234 }
    235 
    236 void 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 
    256 void 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 
    267 void 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 
    282 bool PrerenderContents::PreHandleKeyboardEvent(
    283     const NativeWebKeyboardEvent& event,
    284     bool* is_keyboard_shortcut) {
    285   return false;
    286 }
    287 
    288 void 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 
    357 void 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 
    363 gfx::NativeWindow PrerenderContents::GetMessageBoxRootWindow() {
    364   NOTIMPLEMENTED();
    365   return NULL;
    366 }
    367 
    368 TabContents* PrerenderContents::AsTabContents() {
    369   return NULL;
    370 }
    371 
    372 ExtensionHost* PrerenderContents::AsExtensionHost() {
    373   return NULL;
    374 }
    375 
    376 void PrerenderContents::UpdateInspectorSetting(const std::string& key,
    377                                                const std::string& value) {
    378   RenderViewHostDelegateHelper::UpdateInspectorSetting(profile_, key, value);
    379 }
    380 
    381 void PrerenderContents::ClearInspectorSettings() {
    382   RenderViewHostDelegateHelper::ClearInspectorSettings(profile_);
    383 }
    384 
    385 void PrerenderContents::Close(RenderViewHost* render_view_host) {
    386   Destroy(FINAL_STATUS_CLOSED);
    387 }
    388 
    389 RendererPreferences PrerenderContents::GetRendererPrefs(
    390     Profile* profile) const {
    391   RendererPreferences preferences;
    392   renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile);
    393   return preferences;
    394 }
    395 
    396 WebPreferences PrerenderContents::GetWebkitPrefs() {
    397   return RenderViewHostDelegateHelper::GetWebkitPrefs(profile_,
    398                                                       false);  // is_web_ui
    399 }
    400 
    401 void 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 
    409 void PrerenderContents::CreateNewWidget(int route_id,
    410                                         WebKit::WebPopupType popup_type) {
    411   NOTREACHED();
    412 }
    413 
    414 void PrerenderContents::CreateNewFullscreenWidget(int route_id) {
    415   NOTREACHED();
    416 }
    417 
    418 void 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 
    426 void PrerenderContents::ShowCreatedWidget(int route_id,
    427                                           const gfx::Rect& initial_pos) {
    428   NOTIMPLEMENTED();
    429 }
    430 
    431 void PrerenderContents::ShowCreatedFullscreenWidget(int route_id) {
    432   NOTIMPLEMENTED();
    433 }
    434 
    435 bool 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 
    450 void 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 
    468 void 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 
    482 void PrerenderContents::OnMaybeCancelPrerenderForHTML5Media() {
    483   Destroy(FINAL_STATUS_HTML5_MEDIA);
    484 }
    485 
    486 bool PrerenderContents::AddAliasURL(const GURL& url) {
    487   if (!url.SchemeIs("http"))
    488     return false;
    489   alias_urls_.push_back(url);
    490   return true;
    491 }
    492 
    493 bool PrerenderContents::MatchesURL(const GURL& url) const {
    494   return std::find(alias_urls_.begin(), alias_urls_.end(), url)
    495       != alias_urls_.end();
    496 }
    497 
    498 void PrerenderContents::DidStopLoading() {
    499   has_stopped_loading_ = true;
    500 }
    501 
    502 void PrerenderContents::Destroy(FinalStatus final_status) {
    503   prerender_manager_->RemoveEntry(this);
    504   set_final_status(final_status);
    505   delete this;
    506 }
    507 
    508 void PrerenderContents::OnJSOutOfMemory() {
    509   Destroy(FINAL_STATUS_JS_OUT_OF_MEMORY);
    510 }
    511 
    512 void PrerenderContents::RendererUnresponsive(RenderViewHost* render_view_host,
    513                                              bool is_during_unload) {
    514   Destroy(FINAL_STATUS_RENDERER_UNRESPONSIVE);
    515 }
    516 
    517 
    518 base::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 
    538 void 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