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