summaryrefslogtreecommitdiffstats
path: root/extensions/browser/quota_service.cc
diff options
context:
space:
mode:
authorbenwells@chromium.org <benwells@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-09 17:34:20 +0000
committerbenwells@chromium.org <benwells@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-09 17:34:20 +0000
commit38427a15feef46643bd5d85f56a6885fa2b7a976 (patch)
tree390eea7de7cfe3759c5fcc2170599ff2142c6a8d /extensions/browser/quota_service.cc
parent56c953af6bb2be5fcc2eac19a0f227ca7ed594e0 (diff)
downloadchromium_src-38427a15feef46643bd5d85f56a6885fa2b7a976.zip
chromium_src-38427a15feef46643bd5d85f56a6885fa2b7a976.tar.gz
chromium_src-38427a15feef46643bd5d85f56a6885fa2b7a976.tar.bz2
Moved ExtensionInfoMap and ExtensionsQuotaService to extensions/
In the move these classes became extensions::InfoMap and extensions::QuotaService. TBR=sky BUG=162530 Review URL: https://codereview.chromium.org/63933003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@234131 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions/browser/quota_service.cc')
-rw-r--r--extensions/browser/quota_service.cc192
1 files changed, 192 insertions, 0 deletions
diff --git a/extensions/browser/quota_service.cc b/extensions/browser/quota_service.cc
new file mode 100644
index 0000000..ce21b25
--- /dev/null
+++ b/extensions/browser/quota_service.cc
@@ -0,0 +1,192 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "extensions/browser/quota_service.h"
+
+#include "base/message_loop/message_loop.h"
+#include "base/stl_util.h"
+#include "chrome/browser/extensions/extension_function.h"
+#include "extensions/common/error_utils.h"
+
+namespace {
+
+// If the browser stays open long enough, we reset state once a day.
+// Whatever this value is, it should be an order of magnitude longer than
+// the longest interval in any of the QuotaLimitHeuristics in use.
+const int kPurgeIntervalInDays = 1;
+
+const char kOverQuotaError[] = "This request exceeds the * quota.";
+
+} // namespace
+
+namespace extensions {
+
+QuotaService::QuotaService() {
+ if (base::MessageLoop::current() != NULL) { // Null in unit tests.
+ purge_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromDays(kPurgeIntervalInDays),
+ this,
+ &QuotaService::Purge);
+ }
+}
+
+QuotaService::~QuotaService() {
+ DCHECK(CalledOnValidThread());
+ purge_timer_.Stop();
+ Purge();
+}
+
+std::string QuotaService::Assess(const std::string& extension_id,
+ ExtensionFunction* function,
+ const base::ListValue* args,
+ const base::TimeTicks& event_time) {
+ DCHECK(CalledOnValidThread());
+
+ if (function->ShouldSkipQuotaLimiting())
+ return std::string();
+
+ // Lookup function list for extension.
+ FunctionHeuristicsMap& functions = function_heuristics_[extension_id];
+
+ // Lookup heuristics for function, create if necessary.
+ QuotaLimitHeuristics& heuristics = functions[function->name()];
+ if (heuristics.empty())
+ function->GetQuotaLimitHeuristics(&heuristics);
+
+ if (heuristics.empty())
+ return std::string(); // No heuristic implies no limit.
+
+ ViolationErrorMap::iterator violation_error =
+ violation_errors_.find(extension_id);
+ if (violation_error != violation_errors_.end())
+ return violation_error->second; // Repeat offender.
+
+ QuotaLimitHeuristic* failed_heuristic = NULL;
+ for (QuotaLimitHeuristics::iterator heuristic = heuristics.begin();
+ heuristic != heuristics.end();
+ ++heuristic) {
+ // Apply heuristic to each item (bucket).
+ if (!(*heuristic)->ApplyToArgs(args, event_time)) {
+ failed_heuristic = *heuristic;
+ break;
+ }
+ }
+
+ if (!failed_heuristic)
+ return std::string();
+
+ std::string error = failed_heuristic->GetError();
+ DCHECK_GT(error.length(), 0u);
+
+ PurgeFunctionHeuristicsMap(&functions);
+ function_heuristics_.erase(extension_id);
+ violation_errors_[extension_id] = error;
+ return error;
+}
+
+void QuotaService::PurgeFunctionHeuristicsMap(FunctionHeuristicsMap* map) {
+ FunctionHeuristicsMap::iterator heuristics = map->begin();
+ while (heuristics != map->end()) {
+ STLDeleteElements(&heuristics->second);
+ map->erase(heuristics++);
+ }
+}
+
+void QuotaService::Purge() {
+ DCHECK(CalledOnValidThread());
+ std::map<std::string, FunctionHeuristicsMap>::iterator it =
+ function_heuristics_.begin();
+ for (; it != function_heuristics_.end(); function_heuristics_.erase(it++))
+ PurgeFunctionHeuristicsMap(&it->second);
+}
+
+void QuotaLimitHeuristic::Bucket::Reset(const Config& config,
+ const base::TimeTicks& start) {
+ num_tokens_ = config.refill_token_count;
+ expiration_ = start + config.refill_interval;
+}
+
+void QuotaLimitHeuristic::SingletonBucketMapper::GetBucketsForArgs(
+ const base::ListValue* args,
+ BucketList* buckets) {
+ buckets->push_back(&bucket_);
+}
+
+QuotaLimitHeuristic::QuotaLimitHeuristic(const Config& config,
+ BucketMapper* map,
+ const std::string& name)
+ : config_(config), bucket_mapper_(map), name_(name) {}
+
+QuotaLimitHeuristic::~QuotaLimitHeuristic() {}
+
+bool QuotaLimitHeuristic::ApplyToArgs(const base::ListValue* args,
+ const base::TimeTicks& event_time) {
+ BucketList buckets;
+ bucket_mapper_->GetBucketsForArgs(args, &buckets);
+ for (BucketList::iterator i = buckets.begin(); i != buckets.end(); ++i) {
+ if ((*i)->expiration().is_null()) // A brand new bucket.
+ (*i)->Reset(config_, event_time);
+ if (!Apply(*i, event_time))
+ return false; // It only takes one to spoil it for everyone.
+ }
+ return true;
+}
+
+std::string QuotaLimitHeuristic::GetError() const {
+ return extensions::ErrorUtils::FormatErrorMessage(kOverQuotaError, name_);
+}
+
+QuotaService::SustainedLimit::SustainedLimit(const base::TimeDelta& sustain,
+ const Config& config,
+ BucketMapper* map,
+ const std::string& name)
+ : QuotaLimitHeuristic(config, map, name),
+ repeat_exhaustion_allowance_(sustain.InSeconds() /
+ config.refill_interval.InSeconds()),
+ num_available_repeat_exhaustions_(repeat_exhaustion_allowance_) {}
+
+bool QuotaService::TimedLimit::Apply(Bucket* bucket,
+ const base::TimeTicks& event_time) {
+ if (event_time > bucket->expiration())
+ bucket->Reset(config(), event_time);
+
+ return bucket->DeductToken();
+}
+
+bool QuotaService::SustainedLimit::Apply(Bucket* bucket,
+ const base::TimeTicks& event_time) {
+ if (event_time > bucket->expiration()) {
+ // We reset state for this item and start over again if this request breaks
+ // the bad cycle that was previously being tracked. This occurs if the
+ // state in the bucket expired recently (it has been long enough since the
+ // event that we don't care about the last event), but the bucket still has
+ // tokens (so pressure was not sustained over that time), OR we are more
+ // than 1 full refill interval away from the last event (so even if we used
+ // up all the tokens in the last bucket, nothing happened in the entire
+ // next refill interval, so it doesn't matter).
+ if (bucket->has_tokens() ||
+ event_time > bucket->expiration() + config().refill_interval) {
+ bucket->Reset(config(), event_time);
+ num_available_repeat_exhaustions_ = repeat_exhaustion_allowance_;
+ } else if (--num_available_repeat_exhaustions_ > 0) {
+ // The last interval was saturated with requests, and this is the first
+ // event in the next interval. If this happens
+ // repeat_exhaustion_allowance_ times, it's a violation. Reset the bucket
+ // state to start timing from the end of the last interval (and we'll
+ // deduct the token below) so we can detect this each time it happens.
+ bucket->Reset(config(), bucket->expiration());
+ } else {
+ // No allowances left; this request is a violation.
+ return false;
+ }
+ }
+
+ // We can go negative since we check has_tokens when we get to *next* bucket,
+ // and for the small interval all that matters is whether we used up all the
+ // tokens (which is true if num_tokens_ <= 0).
+ bucket->DeductToken();
+ return true;
+}
+
+} // namespace extensions