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
28DEFINE_WEB_CONTENTS_USER_DATA_KEY(captive_portal::CaptivePortalTabHelper);
29
30namespace captive_portal {
31
32CaptivePortalTabHelper::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
57CaptivePortalTabHelper::~CaptivePortalTabHelper() {
58}
59
60void 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
69void 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
99void 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
128void 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
151void CaptivePortalTabHelper::DidStopLoading(
152    content::RenderViewHost* render_view_host) {
153  DCHECK(CalledOnValidThread());
154
155  login_detector_->OnStoppedLoading();
156}
157
158void 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
190void CaptivePortalTabHelper::OnSSLCertError(const net::SSLInfo& ssl_info) {
191  tab_reloader_->OnSSLCertError(ssl_info);
192}
193
194bool CaptivePortalTabHelper::IsLoginTab() const {
195  return login_detector_->is_login_tab();
196}
197
198void 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
211void CaptivePortalTabHelper::OnCaptivePortalResults(Result previous_result,
212                                                    Result result) {
213  tab_reloader_->OnCaptivePortalResults(previous_result, result);
214  login_detector_->OnCaptivePortalResults(previous_result, result);
215}
216
217void 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
226void CaptivePortalTabHelper::SetIsLoginTab() {
227  login_detector_->SetIsLoginTab();
228}
229
230void CaptivePortalTabHelper::SetTabReloaderForTest(
231    CaptivePortalTabReloader* tab_reloader) {
232  tab_reloader_.reset(tab_reloader);
233}
234
235CaptivePortalTabReloader* CaptivePortalTabHelper::GetTabReloaderForTest() {
236  return tab_reloader_.get();
237}
238
239void 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