Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / captive_portal / captive_portal_service.cc
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_service.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/pref_service.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/pref_names.h"
16 #include "components/captive_portal/captive_portal_types.h"
17 #include "content/public/browser/notification_service.h"
18
19 #if defined(OS_MACOSX)
20 #include "base/mac/mac_util.h"
21 #endif
22
23 #if defined(OS_WIN)
24 #include "base/win/windows_version.h"
25 #endif
26
27 using captive_portal::CaptivePortalResult;
28
29 namespace {
30
31 // Make sure this enum is in sync with CaptivePortalDetectionResult enum
32 // in histograms.xml. This enum is append-only, don't modify existing values.
33 enum CaptivePortalDetectionResult {
34   // There's a confirmed connection to the Internet.
35   DETECTION_RESULT_INTERNET_CONNECTED,
36   // Received a network or HTTP error, or a non-HTTP response.
37   DETECTION_RESULT_NO_RESPONSE,
38   // Encountered a captive portal with a non-HTTPS landing URL.
39   DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL,
40   // Received a network or HTTP error with an HTTPS landing URL.
41   DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL,
42   // Encountered a captive portal with an HTTPS landing URL.
43   DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL,
44   DETECTION_RESULT_COUNT
45 };
46
47 // Records histograms relating to how often captive portal detection attempts
48 // ended with |result| in a row, and for how long |result| was the last result
49 // of a detection attempt.  Recorded both on quit and on a new Result.
50 //
51 // |repeat_count| may be 0 if there were no captive portal checks during
52 // a session.
53 //
54 // |result_duration| is the time between when a captive portal check first
55 // returned |result| and when a check returned a different result, or when the
56 // CaptivePortalService was shut down.
57 void RecordRepeatHistograms(CaptivePortalResult result,
58                             int repeat_count,
59                             base::TimeDelta result_duration) {
60   // Histogram macros can't be used with variable names, since they cache
61   // pointers, so have to use the histogram functions directly.
62
63   // Record number of times the last result was received in a row.
64   base::HistogramBase* result_repeated_histogram =
65       base::Histogram::FactoryGet(
66           "CaptivePortal.ResultRepeated." + CaptivePortalResultToString(result),
67           1,  // min
68           100,  // max
69           100,  // bucket_count
70           base::Histogram::kUmaTargetedHistogramFlag);
71   result_repeated_histogram->Add(repeat_count);
72
73   if (repeat_count == 0)
74     return;
75
76   // Time between first request that returned |result| and now.
77   base::HistogramBase* result_duration_histogram =
78       base::Histogram::FactoryTimeGet(
79           "CaptivePortal.ResultDuration." + CaptivePortalResultToString(result),
80           base::TimeDelta::FromSeconds(1),  // min
81           base::TimeDelta::FromHours(1),  // max
82           50,  // bucket_count
83           base::Histogram::kUmaTargetedHistogramFlag);
84   result_duration_histogram->AddTime(result_duration);
85 }
86
87 int GetHistogramEntryForDetectionResult(
88     const captive_portal::CaptivePortalDetector::Results& results) {
89   bool is_https = results.landing_url.SchemeIs("https");
90   switch (results.result) {
91     case captive_portal::RESULT_INTERNET_CONNECTED:
92       return DETECTION_RESULT_INTERNET_CONNECTED;
93     case captive_portal::RESULT_NO_RESPONSE:
94       return is_https ?
95           DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL :
96           DETECTION_RESULT_NO_RESPONSE;
97     case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
98       return is_https ?
99           DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL :
100           DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL;
101     default:
102       NOTREACHED();
103       return -1;
104   }
105 }
106
107 bool ShouldDeferToNativeCaptivePortalDetection() {
108   // On Windows 8, defer to the native captive portal detection.  OSX Lion and
109   // later also have captive portal detection, but experimentally, this code
110   // works in cases its does not.
111   //
112   // TODO(mmenke): Investigate how well Windows 8's captive portal detection
113   // works.
114 #if defined(OS_WIN)
115   return base::win::GetVersion() >= base::win::VERSION_WIN8;
116 #else
117   return false;
118 #endif
119 }
120
121 }  // namespace
122
123 CaptivePortalService::TestingState CaptivePortalService::testing_state_ =
124     NOT_TESTING;
125
126 class CaptivePortalService::RecheckBackoffEntry : public net::BackoffEntry {
127  public:
128   explicit RecheckBackoffEntry(CaptivePortalService* captive_portal_service)
129       : net::BackoffEntry(
130             &captive_portal_service->recheck_policy().backoff_policy),
131         captive_portal_service_(captive_portal_service) {
132   }
133
134  private:
135   virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE {
136     return captive_portal_service_->GetCurrentTimeTicks();
137   }
138
139   CaptivePortalService* captive_portal_service_;
140
141   DISALLOW_COPY_AND_ASSIGN(RecheckBackoffEntry);
142 };
143
144 CaptivePortalService::RecheckPolicy::RecheckPolicy()
145     : initial_backoff_no_portal_ms(600 * 1000),
146       initial_backoff_portal_ms(20 * 1000) {
147   // Receiving a new Result is considered a success.  All subsequent requests
148   // that get the same Result are considered "failures", so a value of N
149   // means exponential backoff starts after getting a result N + 2 times:
150   // +1 for the initial success, and +1 because N failures are ignored.
151   //
152   // A value of 6 means to start backoff on the 7th failure, which is the 8th
153   // time the same result is received.
154   backoff_policy.num_errors_to_ignore = 6;
155
156   // It doesn't matter what this is initialized to.  It will be overwritten
157   // after the first captive portal detection request.
158   backoff_policy.initial_delay_ms = initial_backoff_no_portal_ms;
159
160   backoff_policy.multiply_factor = 2.0;
161   backoff_policy.jitter_factor = 0.3;
162   backoff_policy.maximum_backoff_ms = 2 * 60 * 1000;
163
164   // -1 means the entry never expires.  This doesn't really matter, as the
165   // service never checks for its expiration.
166   backoff_policy.entry_lifetime_ms = -1;
167
168   backoff_policy.always_use_initial_delay = true;
169 }
170
171 CaptivePortalService::CaptivePortalService(Profile* profile)
172     : profile_(profile),
173       state_(STATE_IDLE),
174       captive_portal_detector_(profile->GetRequestContext()),
175       enabled_(false),
176       last_detection_result_(captive_portal::RESULT_INTERNET_CONNECTED),
177       num_checks_with_same_result_(0),
178       test_url_(captive_portal::CaptivePortalDetector::kDefaultURL) {
179   // The order matters here:
180   // |resolve_errors_with_web_service_| must be initialized and |backoff_entry_|
181   // created before the call to UpdateEnabledState.
182   resolve_errors_with_web_service_.Init(
183       prefs::kAlternateErrorPagesEnabled,
184       profile_->GetPrefs(),
185       base::Bind(&CaptivePortalService::UpdateEnabledState,
186                  base::Unretained(this)));
187   ResetBackoffEntry(last_detection_result_);
188
189   UpdateEnabledState();
190 }
191
192 CaptivePortalService::~CaptivePortalService() {
193 }
194
195 void CaptivePortalService::DetectCaptivePortal() {
196   DCHECK(CalledOnValidThread());
197
198   // If a request is pending or running, do nothing.
199   if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING)
200     return;
201
202   base::TimeDelta time_until_next_check = backoff_entry_->GetTimeUntilRelease();
203
204   // Start asynchronously.
205   state_ = STATE_TIMER_RUNNING;
206   check_captive_portal_timer_.Start(
207       FROM_HERE,
208       time_until_next_check,
209       this,
210       &CaptivePortalService::DetectCaptivePortalInternal);
211 }
212
213 void CaptivePortalService::DetectCaptivePortalInternal() {
214   DCHECK(CalledOnValidThread());
215   DCHECK(state_ == STATE_TIMER_RUNNING || state_ == STATE_IDLE);
216   DCHECK(!TimerRunning());
217
218   state_ = STATE_CHECKING_FOR_PORTAL;
219
220   // When not enabled, just claim there's an Internet connection.
221   if (!enabled_) {
222     // Count this as a success, so the backoff entry won't apply exponential
223     // backoff, but will apply the standard delay.
224     backoff_entry_->InformOfRequest(true);
225     OnResult(captive_portal::RESULT_INTERNET_CONNECTED);
226     return;
227   }
228
229   captive_portal_detector_.DetectCaptivePortal(
230       test_url_, base::Bind(
231           &CaptivePortalService::OnPortalDetectionCompleted,
232           base::Unretained(this)));
233 }
234
235 void CaptivePortalService::OnPortalDetectionCompleted(
236     const captive_portal::CaptivePortalDetector::Results& results) {
237   DCHECK(CalledOnValidThread());
238   DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
239   DCHECK(!TimerRunning());
240   DCHECK(enabled_);
241
242   CaptivePortalResult result = results.result;
243   const base::TimeDelta& retry_after_delta = results.retry_after_delta;
244   base::TimeTicks now = GetCurrentTimeTicks();
245
246   // Record histograms.
247   UMA_HISTOGRAM_ENUMERATION("CaptivePortal.DetectResult",
248                             GetHistogramEntryForDetectionResult(results),
249                             DETECTION_RESULT_COUNT);
250
251   // If this isn't the first captive portal result, record stats.
252   if (!last_check_time_.is_null()) {
253     UMA_HISTOGRAM_LONG_TIMES("CaptivePortal.TimeBetweenChecks",
254                              now - last_check_time_);
255
256     if (last_detection_result_ != result) {
257       // If the last result was different from the result of the latest test,
258       // record histograms about the previous period over which the result was
259       // the same.
260       RecordRepeatHistograms(last_detection_result_,
261                              num_checks_with_same_result_,
262                              now - first_check_time_with_same_result_);
263     }
264   }
265
266   if (last_check_time_.is_null() || result != last_detection_result_) {
267     first_check_time_with_same_result_ = now;
268     num_checks_with_same_result_ = 1;
269
270     // Reset the backoff entry both to update the default time and clear
271     // previous failures.
272     ResetBackoffEntry(result);
273
274     backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
275     // The BackoffEntry is not informed of this request, so there's no delay
276     // before the next request.  This allows for faster login when a captive
277     // portal is first detected.  It can also help when moving between captive
278     // portals.
279   } else {
280     DCHECK_LE(1, num_checks_with_same_result_);
281     ++num_checks_with_same_result_;
282
283     // Requests that have the same Result as the last one are considered
284     // "failures", to trigger backoff.
285     backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
286     backoff_entry_->InformOfRequest(false);
287   }
288
289   last_check_time_ = now;
290
291   OnResult(result);
292 }
293
294 void CaptivePortalService::Shutdown() {
295   DCHECK(CalledOnValidThread());
296   if (enabled_) {
297     RecordRepeatHistograms(
298         last_detection_result_,
299         num_checks_with_same_result_,
300         GetCurrentTimeTicks() - first_check_time_with_same_result_);
301   }
302 }
303
304 void CaptivePortalService::OnResult(CaptivePortalResult result) {
305   DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
306   state_ = STATE_IDLE;
307
308   Results results;
309   results.previous_result = last_detection_result_;
310   results.result = result;
311   last_detection_result_ = result;
312
313   content::NotificationService::current()->Notify(
314       chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
315       content::Source<Profile>(profile_),
316       content::Details<Results>(&results));
317 }
318
319 void CaptivePortalService::ResetBackoffEntry(CaptivePortalResult result) {
320   if (!enabled_ || result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL) {
321     // Use the shorter time when the captive portal service is not enabled, or
322     // behind a captive portal.
323     recheck_policy_.backoff_policy.initial_delay_ms =
324         recheck_policy_.initial_backoff_portal_ms;
325   } else {
326     recheck_policy_.backoff_policy.initial_delay_ms =
327         recheck_policy_.initial_backoff_no_portal_ms;
328   }
329
330   backoff_entry_.reset(new RecheckBackoffEntry(this));
331 }
332
333 void CaptivePortalService::UpdateEnabledState() {
334   DCHECK(CalledOnValidThread());
335   bool enabled_before = enabled_;
336   enabled_ = testing_state_ != DISABLED_FOR_TESTING &&
337              resolve_errors_with_web_service_.GetValue();
338
339   if (testing_state_ != SKIP_OS_CHECK_FOR_TESTING &&
340       ShouldDeferToNativeCaptivePortalDetection()) {
341     enabled_ = false;
342   }
343
344   if (enabled_before == enabled_)
345     return;
346
347   // Clear data used for histograms.
348   num_checks_with_same_result_ = 0;
349   first_check_time_with_same_result_ = base::TimeTicks();
350   last_check_time_ = base::TimeTicks();
351
352   ResetBackoffEntry(last_detection_result_);
353
354   if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING) {
355     // If a captive portal check was running or pending, cancel check
356     // and the timer.
357     check_captive_portal_timer_.Stop();
358     captive_portal_detector_.Cancel();
359     state_ = STATE_IDLE;
360
361     // Since a captive portal request was queued or running, something may be
362     // expecting to receive a captive portal result.
363     DetectCaptivePortal();
364   }
365 }
366
367 base::TimeTicks CaptivePortalService::GetCurrentTimeTicks() const {
368   if (time_ticks_for_testing_.is_null())
369     return base::TimeTicks::Now();
370   else
371     return time_ticks_for_testing_;
372 }
373
374 bool CaptivePortalService::DetectionInProgress() const {
375   return state_ == STATE_CHECKING_FOR_PORTAL;
376 }
377
378 bool CaptivePortalService::TimerRunning() const {
379   return check_captive_portal_timer_.IsRunning();
380 }