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