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 DEFINE_WEB_CONTENTS_USER_DATA_KEY(captive_portal::CaptivePortalTabHelper);
     29 
     30 namespace captive_portal {
     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     bool is_main_frame,
    102     const GURL& url,
    103     content::PageTransition transition_type,
    104     content::RenderViewHost* render_view_host) {
    105   DCHECK(CalledOnValidThread());
    106 
    107   // Ignore subframes.
    108   if (!is_main_frame)
    109     return;
    110 
    111   if (provisional_render_view_host_ == render_view_host) {
    112     tab_reloader_->OnLoadCommitted(pending_error_code_);
    113   } else {
    114     // This may happen if the active RenderView commits a page before a cross
    115     // process navigation cancels the old load.  In this case, the commit of the
    116     // old navigation will cancel the newer one.
    117     OnLoadAborted();
    118 
    119     // Send information about the new load.
    120     tab_reloader_->OnLoadStart(url.SchemeIsSecure());
    121     tab_reloader_->OnLoadCommitted(net::OK);
    122   }
    123 
    124   provisional_render_view_host_ = NULL;
    125   pending_error_code_ = net::OK;
    126 }
    127 
    128 void CaptivePortalTabHelper::DidFailProvisionalLoad(
    129     int64 frame_id,
    130     bool is_main_frame,
    131     const GURL& validated_url,
    132     int error_code,
    133     const string16& error_description,
    134     content::RenderViewHost* render_view_host) {
    135   DCHECK(CalledOnValidThread());
    136 
    137   // Ignore subframes and unexpected RenderViewHosts.
    138   if (!is_main_frame || render_view_host != provisional_render_view_host_)
    139     return;
    140 
    141   // Aborts generally aren't followed by loading an error page, so go ahead and
    142   // reset the state now, to prevent any captive portal checks from triggering.
    143   if (error_code == net::ERR_ABORTED) {
    144     OnLoadAborted();
    145     return;
    146   }
    147 
    148   pending_error_code_ = error_code;
    149 }
    150 
    151 void CaptivePortalTabHelper::DidStopLoading(
    152     content::RenderViewHost* render_view_host) {
    153   DCHECK(CalledOnValidThread());
    154 
    155   login_detector_->OnStoppedLoading();
    156 }
    157 
    158 void CaptivePortalTabHelper::Observe(
    159     int type,
    160     const content::NotificationSource& source,
    161     const content::NotificationDetails& details) {
    162   DCHECK(CalledOnValidThread());
    163   switch (type) {
    164     case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT: {
    165       DCHECK_EQ(web_contents(),
    166                 content::Source<content::WebContents>(source).ptr());
    167 
    168       const content::ResourceRedirectDetails* redirect_details =
    169           content::Details<content::ResourceRedirectDetails>(details).ptr();
    170 
    171       OnRedirect(redirect_details->origin_child_id,
    172                  redirect_details->resource_type,
    173                  redirect_details->new_url);
    174       break;
    175     }
    176     case chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT: {
    177       DCHECK_EQ(profile_, content::Source<Profile>(source).ptr());
    178 
    179       const CaptivePortalService::Results* results =
    180           content::Details<CaptivePortalService::Results>(details).ptr();
    181 
    182       OnCaptivePortalResults(results->previous_result, results->result);
    183       break;
    184     }
    185     default:
    186       NOTREACHED();
    187   }
    188 }
    189 
    190 void CaptivePortalTabHelper::OnSSLCertError(const net::SSLInfo& ssl_info) {
    191   tab_reloader_->OnSSLCertError(ssl_info);
    192 }
    193 
    194 bool CaptivePortalTabHelper::IsLoginTab() const {
    195   return login_detector_->is_login_tab();
    196 }
    197 
    198 void CaptivePortalTabHelper::OnRedirect(int child_id,
    199                                         ResourceType::Type resource_type,
    200                                         const GURL& new_url) {
    201   // Only main frame redirects for the provisional RenderViewHost matter.
    202   if (resource_type != ResourceType::MAIN_FRAME ||
    203       !provisional_render_view_host_ ||
    204       provisional_render_view_host_->GetProcess()->GetID() != child_id) {
    205     return;
    206   }
    207 
    208   tab_reloader_->OnRedirect(new_url.SchemeIsSecure());
    209 }
    210 
    211 void CaptivePortalTabHelper::OnCaptivePortalResults(Result previous_result,
    212                                                     Result result) {
    213   tab_reloader_->OnCaptivePortalResults(previous_result, result);
    214   login_detector_->OnCaptivePortalResults(previous_result, result);
    215 }
    216 
    217 void CaptivePortalTabHelper::OnLoadAborted() {
    218   // No further messages for the cancelled navigation will occur.
    219   provisional_render_view_host_ = NULL;
    220   // May have been aborting the load of an error page.
    221   pending_error_code_ = net::OK;
    222 
    223   tab_reloader_->OnAbort();
    224 }
    225 
    226 void CaptivePortalTabHelper::SetIsLoginTab() {
    227   login_detector_->SetIsLoginTab();
    228 }
    229 
    230 void CaptivePortalTabHelper::SetTabReloaderForTest(
    231     CaptivePortalTabReloader* tab_reloader) {
    232   tab_reloader_.reset(tab_reloader);
    233 }
    234 
    235 CaptivePortalTabReloader* CaptivePortalTabHelper::GetTabReloaderForTest() {
    236   return tab_reloader_.get();
    237 }
    238 
    239 void CaptivePortalTabHelper::OpenLoginTab() {
    240   Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
    241 
    242   // If the Profile doesn't have a tabbed browser window open, do nothing.
    243   if (!browser)
    244     return;
    245 
    246   // Check if the Profile's topmost browser window already has a login tab.
    247   // If so, do nothing.
    248   // TODO(mmenke):  Consider focusing that tab, at least if this is the tab
    249   //                helper for the currently active tab for the profile.
    250   for (int i = 0; i < browser->tab_strip_model()->count(); ++i) {
    251     content::WebContents* web_contents =
    252         browser->tab_strip_model()->GetWebContentsAt(i);
    253     captive_portal::CaptivePortalTabHelper* captive_portal_tab_helper =
    254         captive_portal::CaptivePortalTabHelper::FromWebContents(web_contents);
    255     if (captive_portal_tab_helper->IsLoginTab())
    256       return;
    257   }
    258 
    259   // Otherwise, open a login tab.  Only end up here when a captive portal result
    260   // was received, so it's safe to assume |profile_| has a CaptivePortalService.
    261   content::WebContents* web_contents = chrome::AddSelectedTabWithURL(
    262           browser,
    263           CaptivePortalServiceFactory::GetForProfile(profile_)->test_url(),
    264           content::PAGE_TRANSITION_TYPED);
    265   captive_portal::CaptivePortalTabHelper* captive_portal_tab_helper =
    266       captive_portal::CaptivePortalTabHelper::FromWebContents(web_contents);
    267   captive_portal_tab_helper->SetIsLoginTab();
    268 }
    269 
    270 }  // namespace captive_portal
    271