Home | History | Annotate | Download | only in captive_portal
      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/captive_portal/captive_portal_tab_helper.h"
      6 
      7 #include "base/bind.h"
      8 #include "chrome/browser/captive_portal/captive_portal_login_detector.h"
      9 #include "chrome/browser/captive_portal/captive_portal_service_factory.h"
     10 #include "chrome/browser/captive_portal/captive_portal_tab_reloader.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/ui/browser.h"
     14 #include "chrome/browser/ui/browser_finder.h"
     15 #include "chrome/browser/ui/browser_tabstrip.h"
     16 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     17 #include "content/public/browser/notification_details.h"
     18 #include "content/public/browser/notification_service.h"
     19 #include "content/public/browser/notification_source.h"
     20 #include "content/public/browser/notification_types.h"
     21 #include "content/public/browser/render_frame_host.h"
     22 #include "content/public/browser/render_process_host.h"
     23 #include "content/public/browser/render_view_host.h"
     24 #include "content/public/browser/resource_request_details.h"
     25 #include "content/public/browser/web_contents.h"
     26 #include "net/base/net_errors.h"
     27 #include "net/ssl/ssl_info.h"
     28 
     29 using captive_portal::CaptivePortalResult;
     30 using content::ResourceType;
     31 
     32 DEFINE_WEB_CONTENTS_USER_DATA_KEY(CaptivePortalTabHelper);
     33 
     34 CaptivePortalTabHelper::CaptivePortalTabHelper(
     35     content::WebContents* web_contents)
     36     : content::WebContentsObserver(web_contents),
     37       // web_contents is NULL in unit tests.
     38       profile_(web_contents ? Profile::FromBrowserContext(
     39                                   web_contents->GetBrowserContext())
     40                             : NULL),
     41       tab_reloader_(
     42           new CaptivePortalTabReloader(
     43               profile_,
     44               web_contents,
     45               base::Bind(&CaptivePortalTabHelper::OpenLoginTab,
     46                          base::Unretained(this)))),
     47       login_detector_(new CaptivePortalLoginDetector(profile_)),
     48       web_contents_(web_contents),
     49       pending_error_code_(net::OK),
     50       provisional_render_view_host_(NULL) {
     51   registrar_.Add(this,
     52                  chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
     53                  content::Source<Profile>(profile_));
     54   registrar_.Add(this,
     55                  content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
     56                  content::Source<content::WebContents>(web_contents));
     57 }
     58 
     59 CaptivePortalTabHelper::~CaptivePortalTabHelper() {
     60 }
     61 
     62 void CaptivePortalTabHelper::RenderViewDeleted(
     63     content::RenderViewHost* render_view_host) {
     64   // This can happen when a cross-process navigation is aborted, either by
     65   // pressing stop or by starting a new cross-process navigation that can't
     66   // re-use |provisional_render_view_host_|.  May also happen on a crash.
     67   if (render_view_host == provisional_render_view_host_)
     68     OnLoadAborted();
     69 }
     70 
     71 void CaptivePortalTabHelper::DidStartProvisionalLoadForFrame(
     72     content::RenderFrameHost* render_frame_host,
     73     const GURL& validated_url,
     74     bool is_error_page,
     75     bool is_iframe_srcdoc) {
     76   DCHECK(CalledOnValidThread());
     77 
     78   // Ignore subframes.
     79   if (render_frame_host->GetParent())
     80     return;
     81 
     82   content::RenderViewHost* render_view_host =
     83       render_frame_host->GetRenderViewHost();
     84   if (provisional_render_view_host_) {
     85     // If loading an error page for a previous failure, treat this as part of
     86     // the previous load.  Link Doctor pages act like two error page loads in a
     87     // row.  The second time, provisional_render_view_host_ will be NULL.
     88     if (is_error_page && provisional_render_view_host_ == render_view_host)
     89       return;
     90     // Otherwise, abort the old load.
     91     OnLoadAborted();
     92   }
     93 
     94   provisional_render_view_host_ = render_view_host;
     95   pending_error_code_ = net::OK;
     96 
     97   tab_reloader_->OnLoadStart(validated_url.SchemeIsSecure());
     98 }
     99 
    100 void CaptivePortalTabHelper::DidCommitProvisionalLoadForFrame(
    101     content::RenderFrameHost* render_frame_host,
    102     const GURL& url,
    103     ui::PageTransition transition_type) {
    104   DCHECK(CalledOnValidThread());
    105 
    106   // Ignore subframes.
    107   if (render_frame_host->GetParent())
    108     return;
    109 
    110   if (provisional_render_view_host_ == render_frame_host->GetRenderViewHost()) {
    111     tab_reloader_->OnLoadCommitted(pending_error_code_);
    112   } else {
    113     // This may happen if the active RenderView commits a page before a cross
    114     // process navigation cancels the old load.  In this case, the commit of the
    115     // old navigation will cancel the newer one.
    116     OnLoadAborted();
    117 
    118     // Send information about the new load.
    119     tab_reloader_->OnLoadStart(url.SchemeIsSecure());
    120     tab_reloader_->OnLoadCommitted(net::OK);
    121   }
    122 
    123   provisional_render_view_host_ = NULL;
    124   pending_error_code_ = net::OK;
    125 }
    126 
    127 void CaptivePortalTabHelper::DidFailProvisionalLoad(
    128     content::RenderFrameHost* render_frame_host,
    129     const GURL& validated_url,
    130     int error_code,
    131     const base::string16& error_description) {
    132   DCHECK(CalledOnValidThread());
    133 
    134   // Ignore subframes and unexpected RenderViewHosts.
    135   if (render_frame_host->GetParent() ||
    136       render_frame_host->GetRenderViewHost() != provisional_render_view_host_)
    137     return;
    138 
    139   // Aborts generally aren't followed by loading an error page, so go ahead and
    140   // reset the state now, to prevent any captive portal checks from triggering.
    141   if (error_code == net::ERR_ABORTED) {
    142     OnLoadAborted();
    143     return;
    144   }
    145 
    146   pending_error_code_ = error_code;
    147 }
    148 
    149 void CaptivePortalTabHelper::DidStopLoading(
    150     content::RenderViewHost* render_view_host) {
    151   DCHECK(CalledOnValidThread());
    152 
    153   login_detector_->OnStoppedLoading();
    154 }
    155 
    156 void CaptivePortalTabHelper::Observe(
    157     int type,
    158     const content::NotificationSource& source,
    159     const content::NotificationDetails& details) {
    160   DCHECK(CalledOnValidThread());
    161   switch (type) {
    162     case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
    163       DCHECK_EQ(web_contents(),
    164                 content::Source<content::WebContents>(source).ptr());
    165 
    166       const content::ResourceRedirectDetails* redirect_details =
    167           content::Details<content::ResourceRedirectDetails>(details).ptr();
    168 
    169       OnRedirect(redirect_details->origin_child_id,
    170                  redirect_details->resource_type,
    171                  redirect_details->new_url);
    172       break;
    173     }
    174     case chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT: {
    175       DCHECK_EQ(profile_, content::Source<Profile>(source).ptr());
    176 
    177       const CaptivePortalService::Results* results =
    178           content::Details<CaptivePortalService::Results>(details).ptr();
    179 
    180       OnCaptivePortalResults(results->previous_result, results->result);
    181       break;
    182     }
    183     default:
    184       NOTREACHED();
    185   }
    186 }
    187 
    188 void CaptivePortalTabHelper::OnSSLCertError(const net::SSLInfo& ssl_info) {
    189   tab_reloader_->OnSSLCertError(ssl_info);
    190 }
    191 
    192 bool CaptivePortalTabHelper::IsLoginTab() const {
    193   return login_detector_->is_login_tab();
    194 }
    195 
    196 void CaptivePortalTabHelper::OnRedirect(int child_id,
    197                                         ResourceType resource_type,
    198                                         const GURL& new_url) {
    199   // Only main frame redirects for the provisional RenderViewHost matter.
    200   if (resource_type != content::RESOURCE_TYPE_MAIN_FRAME ||
    201       !provisional_render_view_host_ ||
    202       provisional_render_view_host_->GetProcess()->GetID() != child_id) {
    203     return;
    204   }
    205 
    206   tab_reloader_->OnRedirect(new_url.SchemeIsSecure());
    207 }
    208 
    209 void CaptivePortalTabHelper::OnCaptivePortalResults(
    210     CaptivePortalResult previous_result,
    211     CaptivePortalResult result) {
    212   tab_reloader_->OnCaptivePortalResults(previous_result, result);
    213   login_detector_->OnCaptivePortalResults(previous_result, result);
    214 }
    215 
    216 void CaptivePortalTabHelper::OnLoadAborted() {
    217   // No further messages for the cancelled navigation will occur.
    218   provisional_render_view_host_ = NULL;
    219   // May have been aborting the load of an error page.
    220   pending_error_code_ = net::OK;
    221 
    222   tab_reloader_->OnAbort();
    223 }
    224 
    225 void CaptivePortalTabHelper::SetIsLoginTab() {
    226   login_detector_->SetIsLoginTab();
    227 }
    228 
    229 void CaptivePortalTabHelper::SetTabReloaderForTest(
    230     CaptivePortalTabReloader* tab_reloader) {
    231   tab_reloader_.reset(tab_reloader);
    232 }
    233 
    234 CaptivePortalTabReloader* CaptivePortalTabHelper::GetTabReloaderForTest() {
    235   return tab_reloader_.get();
    236 }
    237 
    238 void CaptivePortalTabHelper::OpenLoginTab() {
    239   Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
    240 
    241   // If the Profile doesn't have a tabbed browser window open, do nothing.
    242   if (!browser)
    243     return;
    244 
    245   // Check if the Profile's topmost browser window already has a login tab.
    246   // If so, do nothing.
    247   // TODO(mmenke):  Consider focusing that tab, at least if this is the tab
    248   //                helper for the currently active tab for the profile.
    249   for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
    250     content::WebContents* web_contents =
    251         browser->tab_strip_model()->GetWebContentsAt(i);
    252     CaptivePortalTabHelper* captive_portal_tab_helper =
    253         CaptivePortalTabHelper::FromWebContents(web_contents);
    254     if (captive_portal_tab_helper->IsLoginTab())
    255       return;
    256   }
    257 
    258   // Otherwise, open a login tab.  Only end up here when a captive portal result
    259   // was received, so it's safe to assume |profile_| has a CaptivePortalService.
    260   content::WebContents* web_contents = chrome::AddSelectedTabWithURL(
    261           browser,
    262           CaptivePortalServiceFactory::GetForProfile(profile_)->test_url(),
    263           ui::PAGE_TRANSITION_TYPED);
    264   CaptivePortalTabHelper* captive_portal_tab_helper =
    265       CaptivePortalTabHelper::FromWebContents(web_contents);
    266   captive_portal_tab_helper->SetIsLoginTab();
    267 }
    268