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