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
29using captive_portal::CaptivePortalResult;
30using content::ResourceType;
31
32DEFINE_WEB_CONTENTS_USER_DATA_KEY(CaptivePortalTabHelper);
33
34CaptivePortalTabHelper::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
59CaptivePortalTabHelper::~CaptivePortalTabHelper() {
60}
61
62void 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
71void 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
100void 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
127void 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
149void CaptivePortalTabHelper::DidStopLoading(
150    content::RenderViewHost* render_view_host) {
151  DCHECK(CalledOnValidThread());
152
153  login_detector_->OnStoppedLoading();
154}
155
156void 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
188void CaptivePortalTabHelper::OnSSLCertError(const net::SSLInfo& ssl_info) {
189  tab_reloader_->OnSSLCertError(ssl_info);
190}
191
192bool CaptivePortalTabHelper::IsLoginTab() const {
193  return login_detector_->is_login_tab();
194}
195
196void 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
209void 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
216void 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
225void CaptivePortalTabHelper::SetIsLoginTab() {
226  login_detector_->SetIsLoginTab();
227}
228
229void CaptivePortalTabHelper::SetTabReloaderForTest(
230    CaptivePortalTabReloader* tab_reloader) {
231  tab_reloader_.reset(tab_reloader);
232}
233
234CaptivePortalTabReloader* CaptivePortalTabHelper::GetTabReloaderForTest() {
235  return tab_reloader_.get();
236}
237
238void 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