OLD | NEW |
---|---|
(Empty) | |
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 "base/message_loop.h" | |
9 #include "chrome/browser/captive_portal/captive_portal_service.h" | |
10 #include "chrome/browser/captive_portal/captive_portal_service_factory.h" | |
11 #include "chrome/browser/profiles/profile.h" | |
12 #include "chrome/browser/ui/browser.h" | |
13 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
14 #include "chrome/common/chrome_notification_types.h" | |
15 #include "content/public/browser/navigation_controller.h" | |
16 #include "content/public/browser/notification_details.h" | |
17 #include "content/public/browser/notification_source.h" | |
18 #include "content/public/browser/web_contents.h" | |
19 #include "net/base/net_errors.h" | |
20 | |
21 namespace captive_portal { | |
22 | |
23 namespace { | |
24 | |
25 // The time we wait for a slow loading SSL page before trigginer a captive | |
cbentzel
2012/04/18 15:12:48
Nit: trigginer
mmenke
2012/04/18 19:15:46
Fixed.
| |
26 // portal check. | |
27 static int kDefaultSlowSSLTime = 30; | |
cbentzel
2012/04/18 15:57:23
Nit: kDefaultSlowSSLTimeSeconds since this does no
mmenke
2012/04/18 19:15:46
Done.
| |
28 | |
29 } // namespace | |
30 | |
31 CaptivePortalTabHelper::~CaptivePortalTabHelper() { | |
32 } | |
33 | |
34 // static | |
35 CaptivePortalTabHelper* CaptivePortalTabHelper::Create( | |
36 TabContentsWrapper* tab_contents) { | |
37 return new CaptivePortalTabHelper( | |
38 tab_contents->profile(), | |
cbentzel
2012/04/18 15:57:23
Nit: is this correct indent? Should just be 4 from
mmenke
2012/04/18 19:15:46
Removed the function.
| |
39 tab_contents->web_contents()); | |
40 } | |
41 | |
42 CaptivePortalTabHelper::CaptivePortalTabHelper( | |
43 Profile* profile, | |
44 content::WebContents* web_contents) | |
45 : ALLOW_THIS_IN_INITIALIZER_LIST(observer_(this, web_contents)), | |
46 profile_(profile), | |
47 state_(STATE_NONE), | |
48 provisional_main_frame_load_(false), | |
49 slow_ssl_load_time_(base::TimeDelta::FromSeconds(kDefaultSlowSSLTime)), | |
50 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { | |
51 registrar_.Add(this, | |
52 chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, | |
53 content::Source<Profile>(profile_)); | |
54 } | |
55 | |
56 void CaptivePortalTabHelper::OnLoadStart(bool is_ssl) { | |
57 DCHECK(CalledOnValidThread()); | |
58 | |
59 provisional_main_frame_load_ = true; | |
60 | |
61 // Don't change state when error pages start loading. If we're going to | |
62 // reload, we want to reload only after the error page has been committed. | |
63 // If this is a login tab, also do nothing. | |
64 if (state_ == STATE_CAPTIVE_PORTAL_LOGIN_PAGE) | |
65 return; | |
66 | |
67 // Otherwise, reset the state, as we're at a new page. | |
68 SetState(STATE_NONE); | |
69 | |
70 // Start the slow load timer for SSL pages. | |
71 // TODO(mmenke): Should we be looking at the port instead? The reason the | |
72 // request never connects is because of the port, not the | |
73 // protocol. | |
74 if (is_ssl) | |
75 SetState(STATE_TIMER_RUNNING); | |
76 } | |
77 | |
78 void CaptivePortalTabHelper::OnLoadCommitted(int net_error) { | |
79 DCHECK(CalledOnValidThread()); | |
80 | |
81 provisional_main_frame_load_ = false; | |
82 | |
83 if (state_ == STATE_CAPTIVE_PORTAL_LOGIN_PAGE || state_ == STATE_NONE) | |
84 return; | |
85 | |
86 // If we don't have a timeout error, reset the state. | |
87 if (net_error != net::ERR_CONNECTION_TIMED_OUT) { | |
88 // TODO(mmenke): If the new URL is the same as the old broken URL, and the | |
89 // request succeeds, we should probably trigger another | |
90 // captive portal check. | |
91 SetState(STATE_NONE); | |
92 return; | |
93 } | |
94 | |
95 // We timed out before the timer expired. This is not terribly likely, but | |
96 // if it does happen, we may have been broken by a captive portal. Go ahead | |
97 // and start the check now, rather than waiting for the timer. | |
98 if (state_ == STATE_TIMER_RUNNING) { | |
99 OnSlowSSLConnect(); | |
100 return; | |
101 } | |
102 | |
103 // If we need to reload, do so asynchronously, since we're in a callback from | |
104 // the WebContents. | |
105 if (state_ == STATE_NEEDS_RELOAD) { | |
106 // If we need to reload the page, do so asynchronously. | |
107 MessageLoop::current()->PostTask( | |
cbentzel
2012/04/18 15:12:48
I'm a little concerned here about doing unnecessar
mmenke
2012/04/18 19:15:46
We only get here when we commit with a connection
| |
108 FROM_HERE, | |
109 base::Bind(&CaptivePortalTabHelper::ReloadTabIfNeeded, | |
110 weak_factory_.GetWeakPtr())); | |
111 } | |
112 } | |
113 | |
114 void CaptivePortalTabHelper::OnAbort() { | |
115 DCHECK(CalledOnValidThread()); | |
116 | |
117 provisional_main_frame_load_ = false; | |
118 if (state_ == STATE_CAPTIVE_PORTAL_LOGIN_PAGE) | |
119 return; | |
120 SetState(STATE_NONE); | |
121 } | |
122 | |
123 void CaptivePortalTabHelper::OnStopLoading() { | |
124 DCHECK(CalledOnValidThread()); | |
125 | |
126 // Check for captive portal when loading stops. This can't be done on | |
127 // DidFinishLoad in the case of captive portals that return the user to | |
128 // the originally requested page, as getting a 204 response does not result | |
129 // in a DidFinishLoad call. | |
130 if (state_ == STATE_CAPTIVE_PORTAL_LOGIN_PAGE) | |
131 CheckForCaptivePortal(); | |
132 } | |
133 | |
134 void CaptivePortalTabHelper::Observe( | |
135 int type, | |
136 const content::NotificationSource& source, | |
137 const content::NotificationDetails& details) { | |
138 DCHECK(CalledOnValidThread()); | |
139 DCHECK_EQ(chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, type); | |
140 DCHECK_EQ(profile_, content::Source<Profile>(source).ptr()); | |
141 | |
142 CaptivePortalService::Results* results = | |
143 content::Details<CaptivePortalService::Results>(details).ptr(); | |
144 | |
145 DCHECK(CalledOnValidThread()); | |
146 | |
147 if (results->result == RESULT_BEHIND_CAPTIVE_PORTAL) { | |
148 if (state_ == STATE_MAYBE_BROKEN_BY_PORTAL) { | |
149 SetState(STATE_BROKEN_BY_PORTAL); | |
150 MaybeOpenCaptivePortalLoginTab(); | |
151 } | |
152 return; | |
153 } | |
154 | |
155 switch (state_) { | |
156 case STATE_MAYBE_BROKEN_BY_PORTAL: | |
157 case STATE_TIMER_RUNNING: | |
158 // If the previous result was BEHIND_CAPTIVE_PORTAL, and we're either in | |
159 // STATE_MAYBE_BROKEN_BY_PORTAL or STATE_TIMER_RUNNING, reload the tab. | |
160 // In the latter case, the tab has yet to commit, but is an SSL page, so | |
161 // if the page ends up at a timeout error, we'll reload it. If not, | |
162 // just reset the state. The helps in the case that a user tries to | |
163 // reload a tab, and then quickly logs in. | |
164 if (results->previous_result == RESULT_BEHIND_CAPTIVE_PORTAL) { | |
165 SetState(STATE_NEEDS_RELOAD); | |
166 return; | |
167 } | |
168 SetState(STATE_NONE); | |
169 return; | |
170 | |
171 case STATE_BROKEN_BY_PORTAL: | |
172 // If we don't have a provisional load going, and we were broken by a | |
173 // captive portal, reload the page. If we don't reload the tab, we keep | |
174 // the current state and reload when the current load finally times out, | |
175 // assuming it does so. | |
176 SetState(STATE_NEEDS_RELOAD); | |
177 return; | |
178 | |
179 case STATE_CAPTIVE_PORTAL_LOGIN_PAGE: | |
180 // There is no captive portal any more, so we're no longer a login tab. | |
181 SetState(STATE_NONE); | |
cbentzel
2012/04/18 15:12:48
Should this politely close itself, or are there co
mmenke
2012/04/18 19:15:46
The user could have logged in from another tab, so
| |
182 return; | |
183 | |
184 case STATE_NEEDS_RELOAD: | |
185 case STATE_NONE: | |
186 // If the tab needs to reload or is in STATE_NONE, do nothing. The reload | |
187 // case shouldn't be very common, since it only lasts until a tab times | |
188 // out, but it's still possible. | |
189 return; | |
190 | |
191 default: | |
192 NOTREACHED(); | |
193 } | |
194 } | |
195 | |
196 void CaptivePortalTabHelper::OnSlowSSLConnect() { | |
197 SetState(STATE_MAYBE_BROKEN_BY_PORTAL); | |
198 } | |
199 | |
200 void CaptivePortalTabHelper::SetState(State new_state) { | |
201 // Stop the timer even when old and new states are the same. | |
202 slow_ssl_load_timer_.Stop(); | |
cbentzel
2012/04/18 15:12:48
Do we only need to do this if state_ == STATE_TIME
mmenke
2012/04/18 19:15:46
Done.
| |
203 | |
cbentzel
2012/04/18 15:12:48
Would it make sense to add DCHECKs here about vali
mmenke
2012/04/18 19:15:46
Done. Valid transitions are:
From anything to NO
| |
204 state_ = new_state; | |
205 | |
206 switch (state_) { | |
207 case STATE_TIMER_RUNNING: | |
208 slow_ssl_load_timer_.Start( | |
209 FROM_HERE, | |
210 slow_ssl_load_time_, | |
211 this, | |
212 &CaptivePortalTabHelper::OnSlowSSLConnect); | |
213 break; | |
214 | |
215 case STATE_MAYBE_BROKEN_BY_PORTAL: | |
216 CheckForCaptivePortal(); | |
217 break; | |
218 | |
219 case STATE_NEEDS_RELOAD: | |
220 // Try to reload the tab now. | |
221 ReloadTabIfNeeded(); | |
222 break; | |
223 | |
224 default: | |
225 break; | |
226 } | |
227 } | |
228 | |
229 void CaptivePortalTabHelper::ReloadTabIfNeeded() { | |
230 // If there's still a provisional load going, or we're no longer in | |
231 // STATE_NEEDS_RELOAD, do nothing. | |
232 if (state_ != STATE_NEEDS_RELOAD || provisional_main_frame_load_) | |
233 return; | |
234 SetState(STATE_NONE); | |
235 ReloadTab(); | |
236 } | |
237 | |
238 void CaptivePortalTabHelper::ReloadTab() { | |
239 observer_.web_contents()->GetController().Reload(true); | |
cbentzel
2012/04/18 15:12:48
Do you want to have the user manually reload if th
mmenke
2012/04/18 19:15:46
Since we failed to commit, we'll always reload the
| |
240 } | |
241 | |
242 void CaptivePortalTabHelper::MaybeOpenCaptivePortalLoginTab() { | |
cbentzel
2012/04/18 15:12:48
I'm wondering if this should move to the service i
mmenke
2012/04/18 19:15:46
I've gone back and forth on this. Currently, the
cbentzel
2012/04/18 21:12:37
That's a good argument for why this is here.
| |
243 Browser* browser = Browser::GetTabbedBrowser(profile_, true); | |
244 // If the Profile doesn't have a tabbed browser window open, do nothing. | |
245 if (!browser) | |
246 return; | |
247 | |
248 // Check if the Profile's topmost browser window already has a login tab. | |
249 // If so, do nothing. | |
250 // TODO(mmenke): Consider focusing that tab, at least if we're already | |
251 // the active tab and in the same Browser window. | |
252 for (int i = 0; i < browser->tab_count(); ++i) { | |
253 TabContentsWrapper* tab_contents_wrapper = | |
254 browser->GetTabContentsWrapperAt(i); | |
255 if (tab_contents_wrapper->captive_portal_tab_helper()->state() == | |
256 CaptivePortalTabHelper::STATE_CAPTIVE_PORTAL_LOGIN_PAGE) { | |
257 return; | |
258 } | |
259 } | |
260 | |
261 // Otherwise, open a login tab. | |
262 TabContentsWrapper* tab_contents_wrapper = | |
263 browser->AddSelectedTabWithURL( | |
264 CaptivePortalServiceFactory::GetForProfile(profile_)->test_url(), | |
265 content::PAGE_TRANSITION_TYPED); | |
cbentzel
2012/04/18 15:57:23
Should we add a new page transition type here? Do
mmenke
2012/04/18 19:15:46
It would be great if the page didn't show up in th
cbentzel
2012/04/18 21:12:37
OK.
| |
266 tab_contents_wrapper->captive_portal_tab_helper()->SetState( | |
267 STATE_CAPTIVE_PORTAL_LOGIN_PAGE); | |
268 } | |
269 | |
270 void CaptivePortalTabHelper::CheckForCaptivePortal() { | |
271 CaptivePortalServiceFactory::GetForProfile(profile_)->CheckForCaptivePortal(); | |
cbentzel
2012/04/18 14:36:31
Is this guaranteed to always exist? Probably safer
mmenke
2012/04/18 14:40:41
Yes, it's guaranteed to exist. I'll go ahead and
| |
272 } | |
273 | |
274 } // namespace captive_portal | |
OLD | NEW |