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