Fix build break
[chromium-blink-merge.git] / chrome / browser / captive_portal / captive_portal_service.cc
blobeb97495aedbceaccfc688b34d21140e91ff367a8
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.
5 #include "chrome/browser/captive_portal/captive_portal_service.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/pref_service.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/common/chrome_notification_types.h"
15 #include "chrome/common/pref_names.h"
16 #include "content/public/browser/notification_service.h"
18 #if defined(OS_MACOSX)
19 #include "base/mac/mac_util.h"
20 #endif
22 #if defined(OS_WIN)
23 #include "base/win/windows_version.h"
24 #endif
26 namespace captive_portal {
28 namespace {
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.
34 // |repeat_count| may be 0 if there were no captive portal checks during
35 // a session.
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.
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);
57 if (repeat_count == 0)
58 return;
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);
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
84 } // namespace
86 CaptivePortalService::TestingState CaptivePortalService::testing_state_ =
87 NOT_TESTING;
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) {
97 private:
98 virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE {
99 return captive_portal_service_->GetCurrentTimeTicks();
102 CaptivePortalService* captive_portal_service_;
104 DISALLOW_COPY_AND_ASSIGN(RecheckBackoffEntry);
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.
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;
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;
123 backoff_policy.multiply_factor = 2.0;
124 backoff_policy.jitter_factor = 0.3;
125 backoff_policy.maximum_backoff_ms = 2 * 60 * 1000;
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;
131 backoff_policy.always_use_initial_delay = true;
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_);
152 UpdateEnabledState();
155 CaptivePortalService::~CaptivePortalService() {
158 void CaptivePortalService::DetectCaptivePortal() {
159 DCHECK(CalledOnValidThread());
161 // If a request is pending or running, do nothing.
162 if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING)
163 return;
165 base::TimeDelta time_until_next_check = backoff_entry_->GetTimeUntilRelease();
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);
176 void CaptivePortalService::DetectCaptivePortalInternal() {
177 DCHECK(CalledOnValidThread());
178 DCHECK(state_ == STATE_TIMER_RUNNING || state_ == STATE_IDLE);
179 DCHECK(!TimerRunning());
181 state_ = STATE_CHECKING_FOR_PORTAL;
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;
192 captive_portal_detector_.DetectCaptivePortal(
193 test_url_, base::Bind(
194 &CaptivePortalService::OnPortalDetectionCompleted,
195 base::Unretained(this)));
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_);
205 Result result = results.result;
206 const base::TimeDelta& retry_after_delta = results.retry_after_delta;
207 base::TimeTicks now = GetCurrentTimeTicks();
209 // Record histograms.
210 UMA_HISTOGRAM_ENUMERATION("CaptivePortal.DetectResult",
211 result,
212 RESULT_COUNT);
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_);
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_);
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;
233 // Reset the backoff entry both to update the default time and clear
234 // previous failures.
235 ResetBackoffEntry(result);
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_;
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);
252 last_check_time_ = now;
254 OnResult(result);
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_);
267 void CaptivePortalService::OnResult(Result result) {
268 DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
269 state_ = STATE_IDLE;
271 Results results;
272 results.previous_result = last_detection_result_;
273 results.result = result;
274 last_detection_result_ = result;
276 content::NotificationService::current()->Notify(
277 chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
278 content::Source<Profile>(profile_),
279 content::Details<Results>(&results));
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;
293 backoff_entry_.reset(new RecheckBackoffEntry(this));
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();
302 if (testing_state_ != SKIP_OS_CHECK_FOR_TESTING &&
303 HasNativeCaptivePortalDetection()) {
304 enabled_ = false;
307 if (enabled_before == enabled_)
308 return;
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();
315 ResetBackoffEntry(last_detection_result_);
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;
324 // Since a captive portal request was queued or running, something may be
325 // expecting to receive a captive portal result.
326 DetectCaptivePortal();
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_;
337 bool CaptivePortalService::DetectionInProgress() const {
338 return state_ == STATE_CHECKING_FOR_PORTAL;
341 bool CaptivePortalService::TimerRunning() const {
342 return check_captive_portal_timer_.IsRunning();
345 } // namespace captive_portal