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