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