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.
5 #include "chrome/browser/captive_portal/captive_portal_tab_helper.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"
29 using captive_portal::CaptivePortalResult
;
30 using content::ResourceType
;
32 DEFINE_WEB_CONTENTS_USER_DATA_KEY(CaptivePortalTabHelper
);
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())
42 new CaptivePortalTabReloader(
45 base::Bind(&CaptivePortalTabHelper::OpenLoginTabForWebContents
,
46 web_contents
, false))),
47 login_detector_(new CaptivePortalLoginDetector(profile_
)),
48 pending_error_code_(net::OK
),
49 provisional_render_view_host_(NULL
) {
51 chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT
,
52 content::Source
<Profile
>(profile_
));
54 content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT
,
55 content::Source
<content::WebContents
>(web_contents
));
58 CaptivePortalTabHelper::~CaptivePortalTabHelper() {
61 void CaptivePortalTabHelper::RenderViewDeleted(
62 content::RenderViewHost
* render_view_host
) {
63 // This can happen when a cross-process navigation is aborted, either by
64 // pressing stop or by starting a new cross-process navigation that can't
65 // re-use |provisional_render_view_host_|. May also happen on a crash.
66 if (render_view_host
== provisional_render_view_host_
)
70 void CaptivePortalTabHelper::DidStartProvisionalLoadForFrame(
71 content::RenderFrameHost
* render_frame_host
,
72 const GURL
& validated_url
,
74 bool is_iframe_srcdoc
) {
75 DCHECK(CalledOnValidThread());
78 if (render_frame_host
->GetParent())
81 content::RenderViewHost
* render_view_host
=
82 render_frame_host
->GetRenderViewHost();
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
)
89 // Otherwise, abort the old load.
93 provisional_render_view_host_
= render_view_host
;
94 pending_error_code_
= net::OK
;
96 tab_reloader_
->OnLoadStart(validated_url
.SchemeIsSecure());
99 void CaptivePortalTabHelper::DidCommitProvisionalLoadForFrame(
100 content::RenderFrameHost
* render_frame_host
,
102 ui::PageTransition transition_type
) {
103 DCHECK(CalledOnValidThread());
106 if (render_frame_host
->GetParent())
109 if (provisional_render_view_host_
== render_frame_host
->GetRenderViewHost()) {
110 tab_reloader_
->OnLoadCommitted(pending_error_code_
);
112 // This may happen if the active RenderView commits a page before a cross
113 // process navigation cancels the old load. In this case, the commit of the
114 // old navigation will cancel the newer one.
117 // Send information about the new load.
118 tab_reloader_
->OnLoadStart(url
.SchemeIsSecure());
119 tab_reloader_
->OnLoadCommitted(net::OK
);
122 provisional_render_view_host_
= NULL
;
123 pending_error_code_
= net::OK
;
126 void CaptivePortalTabHelper::DidFailProvisionalLoad(
127 content::RenderFrameHost
* render_frame_host
,
128 const GURL
& validated_url
,
130 const base::string16
& error_description
) {
131 DCHECK(CalledOnValidThread());
133 // Ignore subframes and unexpected RenderViewHosts.
134 if (render_frame_host
->GetParent() ||
135 render_frame_host
->GetRenderViewHost() != provisional_render_view_host_
)
138 // Aborts generally aren't followed by loading an error page, so go ahead and
139 // reset the state now, to prevent any captive portal checks from triggering.
140 if (error_code
== net::ERR_ABORTED
) {
145 pending_error_code_
= error_code
;
148 void CaptivePortalTabHelper::DidStopLoading() {
149 DCHECK(CalledOnValidThread());
151 login_detector_
->OnStoppedLoading();
154 void CaptivePortalTabHelper::Observe(
156 const content::NotificationSource
& source
,
157 const content::NotificationDetails
& details
) {
158 DCHECK(CalledOnValidThread());
160 case content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT
: {
161 DCHECK_EQ(web_contents(),
162 content::Source
<content::WebContents
>(source
).ptr());
164 const content::ResourceRedirectDetails
* redirect_details
=
165 content::Details
<content::ResourceRedirectDetails
>(details
).ptr();
167 OnRedirect(redirect_details
->origin_child_id
,
168 redirect_details
->resource_type
,
169 redirect_details
->new_url
);
172 case chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT
: {
173 DCHECK_EQ(profile_
, content::Source
<Profile
>(source
).ptr());
175 const CaptivePortalService::Results
* results
=
176 content::Details
<CaptivePortalService::Results
>(details
).ptr();
178 OnCaptivePortalResults(results
->previous_result
, results
->result
);
186 void CaptivePortalTabHelper::OnSSLCertError(const net::SSLInfo
& ssl_info
) {
187 tab_reloader_
->OnSSLCertError(ssl_info
);
190 bool CaptivePortalTabHelper::IsLoginTab() const {
191 return login_detector_
->is_login_tab();
195 void CaptivePortalTabHelper::OpenLoginTabForWebContents(
196 content::WebContents
* web_contents
,
198 Browser
* browser
= chrome::FindBrowserWithWebContents(web_contents
);
200 // If the Profile doesn't have a tabbed browser window open, do nothing.
204 // Check if the Profile's topmost browser window already has a login tab.
205 // If so, do nothing.
206 // TODO(mmenke): Consider focusing that tab, at least if this is the tab
207 // helper for the currently active tab for the profile.
208 for (int i
= 0; i
< browser
->tab_strip_model()->count(); ++i
) {
209 content::WebContents
* contents
=
210 browser
->tab_strip_model()->GetWebContentsAt(i
);
211 CaptivePortalTabHelper
* captive_portal_tab_helper
=
212 CaptivePortalTabHelper::FromWebContents(contents
);
213 if (captive_portal_tab_helper
->IsLoginTab()) {
215 browser
->tab_strip_model()->ActivateTabAt(i
, false);
220 // Otherwise, open a login tab. Only end up here when a captive portal result
221 // was received, so it's safe to assume profile has a CaptivePortalService.
222 content::WebContents
* new_contents
= chrome::AddSelectedTabWithURL(
224 CaptivePortalServiceFactory::GetForProfile(
225 browser
->profile())->test_url(),
226 ui::PAGE_TRANSITION_TYPED
);
227 CaptivePortalTabHelper
* captive_portal_tab_helper
=
228 CaptivePortalTabHelper::FromWebContents(new_contents
);
229 captive_portal_tab_helper
->SetIsLoginTab();
232 void CaptivePortalTabHelper::OnRedirect(int child_id
,
233 ResourceType resource_type
,
234 const GURL
& new_url
) {
235 // Only main frame redirects for the provisional RenderViewHost matter.
236 if (resource_type
!= content::RESOURCE_TYPE_MAIN_FRAME
||
237 !provisional_render_view_host_
||
238 provisional_render_view_host_
->GetProcess()->GetID() != child_id
) {
242 tab_reloader_
->OnRedirect(new_url
.SchemeIsSecure());
245 void CaptivePortalTabHelper::OnCaptivePortalResults(
246 CaptivePortalResult previous_result
,
247 CaptivePortalResult result
) {
248 tab_reloader_
->OnCaptivePortalResults(previous_result
, result
);
249 login_detector_
->OnCaptivePortalResults(previous_result
, result
);
252 void CaptivePortalTabHelper::OnLoadAborted() {
253 // No further messages for the cancelled navigation will occur.
254 provisional_render_view_host_
= NULL
;
255 // May have been aborting the load of an error page.
256 pending_error_code_
= net::OK
;
258 tab_reloader_
->OnAbort();
261 void CaptivePortalTabHelper::SetIsLoginTab() {
262 login_detector_
->SetIsLoginTab();
265 void CaptivePortalTabHelper::SetTabReloaderForTest(
266 CaptivePortalTabReloader
* tab_reloader
) {
267 tab_reloader_
.reset(tab_reloader
);
270 CaptivePortalTabReloader
* CaptivePortalTabHelper::GetTabReloaderForTest() {
271 return tab_reloader_
.get();