summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbattre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-03 09:20:25 +0000
committerbattre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-03 09:20:25 +0000
commitfd50e7b218e48d2299664f71b3af13e4acae1868 (patch)
tree6e0a1b640147e352b2d9849fcd47148dc5cc5d9e
parent52447df4674e60c7dc78fa6742885f8ae470a517 (diff)
downloadchromium_src-fd50e7b218e48d2299664f71b3af13e4acae1868.zip
chromium_src-fd50e7b218e48d2299664f71b3af13e4acae1868.tar.gz
chromium_src-fd50e7b218e48d2299664f71b3af13e4acae1868.tar.bz2
Trigger warning if extensions call webRequest.handlerBehaviorChanged too frequently
BUG=Write extension that calls webRequest.handlerBehaviorChanged 10 times in short succession - already simulated in unittest. TEST=no Review URL: http://codereview.chromium.org/8417038 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@108441 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/extension_function.cc5
-rw-r--r--chrome/browser/extensions/extension_function.h4
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.cc15
-rw-r--r--chrome/browser/extensions/extension_function_dispatcher.h2
-rw-r--r--chrome/browser/extensions/extension_info_map.h6
-rw-r--r--chrome/browser/extensions/extension_warning_set.cc4
-rw-r--r--chrome/browser/extensions/extension_warning_set.h3
-rw-r--r--chrome/browser/extensions/extension_webrequest_api.cc135
-rw-r--r--chrome/browser/extensions/extension_webrequest_api.h22
-rw-r--r--chrome/browser/extensions/extension_webrequest_time_tracker.cc4
-rw-r--r--chrome/browser/extensions/extensions_quota_service.cc8
-rw-r--r--chrome/browser/extensions/extensions_quota_service.h15
-rw-r--r--chrome/renderer/resources/extensions/extension_process_bindings.js7
13 files changed, 219 insertions, 11 deletions
diff --git a/chrome/browser/extensions/extension_function.cc b/chrome/browser/extensions/extension_function.cc
index 28d6407..72728aa 100644
--- a/chrome/browser/extensions/extension_function.cc
+++ b/chrome/browser/extensions/extension_function.cc
@@ -64,6 +64,11 @@ IOThreadExtensionFunction* ExtensionFunction::AsIOThreadExtensionFunction() {
return NULL;
}
+void ExtensionFunction::OnQuotaExceeded() {
+ error_ = QuotaLimitHeuristic::kGenericOverQuotaError;
+ SendResponse(false);
+}
+
void ExtensionFunction::SetArgs(const ListValue* args) {
DCHECK(!args_.get()); // Should only be called once.
args_.reset(args->DeepCopy());
diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h
index 7e3cd3a..21fd897 100644
--- a/chrome/browser/extensions/extension_function.h
+++ b/chrome/browser/extensions/extension_function.h
@@ -84,6 +84,10 @@ class ExtensionFunction
virtual void GetQuotaLimitHeuristics(
std::list<QuotaLimitHeuristic*>* heuristics) const {}
+ // Called when the quota limit has been exceeded. The default implementation
+ // returns an error.
+ virtual void OnQuotaExceeded();
+
// Specifies the raw arguments to the function, as a JSON value.
virtual void SetArgs(const base::ListValue* args);
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index 5e8b4bf..eb6589e 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -519,7 +519,7 @@ void ExtensionFunctionDispatcher::ResetFunctions() {
// static
void ExtensionFunctionDispatcher::DispatchOnIOThread(
- const ExtensionInfoMap* extension_info_map,
+ ExtensionInfoMap* extension_info_map,
void* profile,
int render_process_id,
base::WeakPtr<ChromeRenderMessageFilter> ipc_sender,
@@ -552,7 +552,14 @@ void ExtensionFunctionDispatcher::DispatchOnIOThread(
function_io->set_extension_info_map(extension_info_map);
function->set_include_incognito(
extension_info_map->IsIncognitoEnabled(extension->id()));
- function->Run();
+
+ ExtensionsQuotaService* quota = extension_info_map->quota_service();
+ if (quota->Assess(extension->id(), function, &params.arguments,
+ base::TimeTicks::Now())) {
+ function->Run();
+ } else {
+ function->OnQuotaExceeded();
+ }
}
ExtensionFunctionDispatcher::ExtensionFunctionDispatcher(Profile* profile,
@@ -647,9 +654,7 @@ void ExtensionFunctionDispatcher::Dispatch(
function->Run();
} else {
- render_view_host->Send(new ExtensionMsg_Response(
- render_view_host->routing_id(), function->request_id(), false,
- std::string(), QuotaLimitHeuristic::kGenericOverQuotaError));
+ function->OnQuotaExceeded();
}
}
diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h
index f700d8f..a45efc4c 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.h
+++ b/chrome/browser/extensions/extension_function_dispatcher.h
@@ -77,7 +77,7 @@ class ExtensionFunctionDispatcher
// Dispatches an IO-thread extension function. Only used for specific
// functions that must be handled on the IO-thread.
static void DispatchOnIOThread(
- const ExtensionInfoMap* extension_info_map,
+ ExtensionInfoMap* extension_info_map,
void* profile,
int render_process_id,
base::WeakPtr<ChromeRenderMessageFilter> ipc_sender,
diff --git a/chrome/browser/extensions/extension_info_map.h b/chrome/browser/extensions/extension_info_map.h
index c7d5726..6aaf6b0 100644
--- a/chrome/browser/extensions/extension_info_map.h
+++ b/chrome/browser/extensions/extension_info_map.h
@@ -12,6 +12,7 @@
#include "base/basictypes.h"
#include "base/time.h"
#include "base/memory/ref_counted.h"
+#include "chrome/browser/extensions/extensions_quota_service.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_set.h"
@@ -72,6 +73,8 @@ class ExtensionInfoMap : public base::RefCountedThreadSafe<ExtensionInfoMap> {
const GURL& origin, int process_id,
ExtensionAPIPermission::ID permission) const;
+ ExtensionsQuotaService* quota_service() { return &quota_service_; }
+
private:
// Extra dynamic data related to an extension.
struct ExtraData;
@@ -86,6 +89,9 @@ class ExtensionInfoMap : public base::RefCountedThreadSafe<ExtensionInfoMap> {
typedef std::multimap<std::string, int> ExtensionProcessIDMap;
ExtensionProcessIDMap extension_process_ids_;
+
+ // Used by dispatchers to limit API quota for individual extensions.
+ ExtensionsQuotaService quota_service_;
};
#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_INFO_MAP_H_
diff --git a/chrome/browser/extensions/extension_warning_set.cc b/chrome/browser/extensions/extension_warning_set.cc
index 0c4c8ef2..ce442d0 100644
--- a/chrome/browser/extensions/extension_warning_set.cc
+++ b/chrome/browser/extensions/extension_warning_set.cc
@@ -83,6 +83,10 @@ string16 ExtensionWarningSet::GetLocalizedWarning(
l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
case kNetworkConflict:
return l10n_util::GetStringUTF16(IDS_EXTENSION_WARNINGS_NETWORK_CONFLICT);
+ case kRepeatedCacheFlushes:
+ return l10n_util::GetStringFUTF16(
+ IDS_EXTENSION_WARNINGS_NETWORK_DELAY,
+ l10n_util::GetStringUTF16(IDS_PRODUCT_NAME));
}
NOTREACHED(); // Switch statement has no default branch.
return string16();
diff --git a/chrome/browser/extensions/extension_warning_set.h b/chrome/browser/extensions/extension_warning_set.h
index 56178c2..fb72374 100644
--- a/chrome/browser/extensions/extension_warning_set.h
+++ b/chrome/browser/extensions/extension_warning_set.h
@@ -29,6 +29,9 @@ class ExtensionWarningSet {
// This extension failed to modify a network request because the
// modification conflicted with a modification of another extension.
kNetworkConflict,
+ // The extension repeatedly flushed WebKit's in-memory cache, which slows
+ // down the overall performance.
+ kRepeatedCacheFlushes,
kMaxWarningType
};
diff --git a/chrome/browser/extensions/extension_webrequest_api.cc b/chrome/browser/extensions/extension_webrequest_api.cc
index bc6b493..f1cf685 100644
--- a/chrome/browser/extensions/extension_webrequest_api.cc
+++ b/chrome/browser/extensions/extension_webrequest_api.cc
@@ -10,6 +10,7 @@
#include "base/json/json_writer.h"
#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
+#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
@@ -309,6 +310,10 @@ void NotifyWebRequestAPIUsed(void* profile_id, const Extension* extension) {
}
}
+void ClearCacheOnNavigationOnUI() {
+ WebCacheManager::GetInstance()->ClearCacheOnNavigation();
+}
+
} // namespace
// Represents a single unique listener to an event, along with whatever filter
@@ -525,6 +530,9 @@ int ExtensionWebRequestEventRouter::OnBeforeRequest(
if (!profile)
return net::OK;
+ if (IsPageLoad(request))
+ NotifyPageLoad();
+
if (!HasWebRequestScheme(request->url()))
return net::OK;
@@ -1066,6 +1074,34 @@ void ExtensionWebRequestEventRouter::OnOTRProfileDestroyed(
cross_profile_map_.erase(original_profile);
}
+void ExtensionWebRequestEventRouter::AddCallbackForPageLoad(
+ const base::Closure& callback) {
+ callbacks_for_page_load_.push_back(callback);
+}
+
+bool ExtensionWebRequestEventRouter::IsPageLoad(
+ net::URLRequest* request) const {
+ bool is_main_frame = false;
+ int64 frame_id = -1;
+ int tab_id = -1;
+ int window_id = -1;
+ ResourceType::Type resource_type = ResourceType::LAST_TYPE;
+
+ ExtractRequestInfoDetails(request, &is_main_frame, &frame_id, &tab_id,
+ &window_id, &resource_type);
+
+ return resource_type == ResourceType::MAIN_FRAME;
+}
+
+void ExtensionWebRequestEventRouter::NotifyPageLoad() {
+ for (CallbacksForPageLoad::const_iterator i =
+ callbacks_for_page_load_.begin();
+ i != callbacks_for_page_load_.end(); ++i) {
+ i->Run();
+ }
+ callbacks_for_page_load_.clear();
+}
+
void ExtensionWebRequestEventRouter::GetMatchingListenersImpl(
void* profile,
ExtensionInfoMap* extension_info_map,
@@ -1556,6 +1592,73 @@ void ExtensionWebRequestEventRouter::ClearSignaled(uint64 request_id,
iter->second &= ~event_type;
}
+// Special QuotaLimitHeuristic for WebRequestHandlerBehaviorChanged.
+//
+// Each call of webRequest.handlerBehaviorChanged() clears the in-memory cache
+// of WebKit at the time of the next page load (top level navigation event).
+// This quota heuristic is intended to limit the number of times the cache is
+// cleared by an extension.
+//
+// As we want to account for the number of times the cache is really cleared
+// (opposed to the number of times webRequest.handlerBehaviorChanged() is
+// called), we cannot decide whether a call of
+// webRequest.handlerBehaviorChanged() should trigger a quota violation at the
+// time it is called. Instead we only decrement the bucket counter at the time
+// when the cache is cleared (when page loads happen).
+class ClearCacheQuotaHeuristic : public QuotaLimitHeuristic {
+ public:
+ ClearCacheQuotaHeuristic(const Config& config, BucketMapper* map)
+ : QuotaLimitHeuristic(config, map),
+ callback_registered_(false),
+ weak_ptr_factory_(this) {}
+ virtual ~ClearCacheQuotaHeuristic() {}
+ virtual bool Apply(Bucket* bucket,
+ const base::TimeTicks& event_time) OVERRIDE;
+
+ private:
+ // Callback that is triggered by the ExtensionWebRequestEventRouter on a page
+ // load.
+ //
+ // We don't need to take care of the life time of |bucket|: It is owned by the
+ // BucketMapper of our base class in |QuotaLimitHeuristic::bucket_mapper_|. As
+ // long as |this| exists, the respective BucketMapper and its bucket will
+ // exist as well.
+ void OnPageLoad(Bucket* bucket);
+
+ // Flag to prevent that we register more than one call back in-between
+ // clearing the cache.
+ bool callback_registered_;
+
+ base::WeakPtrFactory<ClearCacheQuotaHeuristic> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClearCacheQuotaHeuristic);
+};
+
+bool ClearCacheQuotaHeuristic::Apply(Bucket* bucket,
+ const base::TimeTicks& event_time) {
+ if (event_time > bucket->expiration())
+ bucket->Reset(config(), event_time);
+
+ // Call bucket->DeductToken() on a new page load, this is when
+ // webRequest.handlerBehaviorChanged() clears the cache.
+ if (!callback_registered_) {
+ ExtensionWebRequestEventRouter::GetInstance()->AddCallbackForPageLoad(
+ base::Bind(&ClearCacheQuotaHeuristic::OnPageLoad,
+ weak_ptr_factory_.GetWeakPtr(),
+ bucket));
+ callback_registered_ = true;
+ }
+
+ // We only check whether tokens are left here. Deducting a token happens in
+ // OnPageLoad().
+ return bucket->has_tokens();
+}
+
+void ClearCacheQuotaHeuristic::OnPageLoad(Bucket* bucket) {
+ callback_registered_ = false;
+ bucket->DeductToken();
+}
+
bool WebRequestAddEventListener::RunImpl() {
// Argument 0 is the callback, which we don't use here.
@@ -1717,10 +1820,40 @@ bool WebRequestEventHandled::RunImpl() {
}
bool WebRequestHandlerBehaviorChanged::RunImpl() {
- WebCacheManager::GetInstance()->ClearCacheOnNavigation();
+ BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
+ base::Bind(&ClearCacheOnNavigationOnUI));
return true;
}
+void WebRequestHandlerBehaviorChanged::GetQuotaLimitHeuristics(
+ std::list<QuotaLimitHeuristic*>* heuristics) const {
+ QuotaLimitHeuristic::Config config = {
+ 20, // Refill 20 tokens per interval.
+ base::TimeDelta::FromMinutes(10) // 10 minutes refill interval.
+ };
+ QuotaLimitHeuristic::BucketMapper* bucket_mapper =
+ new QuotaLimitHeuristic::SingletonBucketMapper();
+ ClearCacheQuotaHeuristic* heuristic =
+ new ClearCacheQuotaHeuristic(config, bucket_mapper);
+ heuristics->push_back(heuristic);
+}
+
+void WebRequestHandlerBehaviorChanged::OnQuotaExceeded() {
+ // Post warning message.
+ std::set<std::string> extension_ids;
+ extension_ids.insert(extension_id());
+ BrowserThread::PostTask(
+ BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&ExtensionWarningSet::NotifyWarningsOnUI,
+ profile_id(),
+ extension_ids,
+ ExtensionWarningSet::kRepeatedCacheFlushes));
+
+ // Continue gracefully.
+ Run();
+}
+
void SendExtensionWebRequestStatusToHost(RenderProcessHost* host) {
Profile* profile = Profile::FromBrowserContext(host->browser_context());
if (!profile || !profile->GetExtensionService())
diff --git a/chrome/browser/extensions/extension_webrequest_api.h b/chrome/browser/extensions/extension_webrequest_api.h
index a238055..0e9d03e 100644
--- a/chrome/browser/extensions/extension_webrequest_api.h
+++ b/chrome/browser/extensions/extension_webrequest_api.h
@@ -271,6 +271,10 @@ class ExtensionWebRequestEventRouter {
void OnOTRProfileDestroyed(void* original_profile,
void* otr_profile);
+ // Registers a |callback| that is executed when the next page load happens.
+ // The callback is then deleted.
+ void AddCallbackForPageLoad(const base::Closure& callback);
+
private:
friend struct DefaultSingletonTraits<ExtensionWebRequestEventRouter>;
struct EventListener;
@@ -281,6 +285,7 @@ class ExtensionWebRequestEventRouter {
// Map of request_id -> bit vector of EventTypes already signaled
typedef std::map<uint64, int> SignaledRequestMap;
typedef std::map<void*, void*> CrossProfileMap;
+ typedef std::list<base::Closure> CallbacksForPageLoad;
ExtensionWebRequestEventRouter();
~ExtensionWebRequestEventRouter();
@@ -369,6 +374,12 @@ class ExtensionWebRequestEventRouter {
BlockedRequest* request,
std::set<std::string>* conflicting_extensions) const;
+ // Returns whether |request| represents a top level window navigation.
+ bool IsPageLoad(net::URLRequest* request) const;
+
+ // Called on a page load to process all registered callbacks.
+ void NotifyPageLoad();
+
// A map for each profile that maps an event name to a set of extensions that
// are listening to that event.
ListenerMap listeners_;
@@ -389,6 +400,8 @@ class ExtensionWebRequestEventRouter {
// webRequest API.
scoped_ptr<ExtensionWebRequestTimeTracker> request_time_tracker_;
+ CallbacksForPageLoad callbacks_for_page_load_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionWebRequestEventRouter);
};
@@ -404,11 +417,18 @@ class WebRequestEventHandled : public SyncIOThreadExtensionFunction {
DECLARE_EXTENSION_FUNCTION_NAME("experimental.webRequest.eventHandled");
};
-class WebRequestHandlerBehaviorChanged : public AsyncExtensionFunction {
+class WebRequestHandlerBehaviorChanged : public SyncIOThreadExtensionFunction {
public:
virtual bool RunImpl();
DECLARE_EXTENSION_FUNCTION_NAME(
"experimental.webRequest.handlerBehaviorChanged");
+
+ private:
+ virtual void GetQuotaLimitHeuristics(
+ std::list<QuotaLimitHeuristic*>* heuristics) const OVERRIDE;
+ // Handle quota exceeded gracefully: Only warn the user but still execute the
+ // function.
+ virtual void OnQuotaExceeded() OVERRIDE;
};
// Send updates to |host| with information about what webRequest-related
diff --git a/chrome/browser/extensions/extension_webrequest_time_tracker.cc b/chrome/browser/extensions/extension_webrequest_time_tracker.cc
index 418dbd8..3fc2a79 100644
--- a/chrome/browser/extensions/extension_webrequest_time_tracker.cc
+++ b/chrome/browser/extensions/extension_webrequest_time_tracker.cc
@@ -64,7 +64,7 @@ void DefaultDelegate::NotifyExcessiveDelays(
// BrowserThread::PostTask(
// BrowserThread::UI,
// FROM_HERE,
- // base::Bind(&NotifyNetworkDelaysOnUI,
+ // base::Bind(&ExtensionWarningSet::NotifyWarningsOnUI,
// profile,
// extension_ids,
// ExtensionWarningSet::kNetworkDelay));
@@ -81,7 +81,7 @@ void DefaultDelegate::NotifyModerateDelays(
// BrowserThread::PostTask(
// BrowserThread::UI,
// FROM_HERE,
- // base::Bind(&NotifyNetworkDelaysOnUI,
+ // base::Bind(&ExtensionWarningSet::NotifyWarningsOnUI,
// profile,
// extension_ids,
// ExtensionWarningSet::kNetworkDelay));
diff --git a/chrome/browser/extensions/extensions_quota_service.cc b/chrome/browser/extensions/extensions_quota_service.cc
index 6c441b1..0f7a9a2 100644
--- a/chrome/browser/extensions/extensions_quota_service.cc
+++ b/chrome/browser/extensions/extensions_quota_service.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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.
@@ -84,6 +84,12 @@ void QuotaLimitHeuristic::Bucket::Reset(const Config& config,
expiration_ = start + config.refill_interval;
}
+void QuotaLimitHeuristic::SingletonBucketMapper::GetBucketsForArgs(
+ const ListValue* args,
+ BucketList* buckets) {
+ buckets->push_back(&bucket_);
+}
+
QuotaLimitHeuristic::QuotaLimitHeuristic(const Config& config,
BucketMapper* map)
: config_(config), bucket_mapper_(map) {
diff --git a/chrome/browser/extensions/extensions_quota_service.h b/chrome/browser/extensions/extensions_quota_service.h
index a0329b2a..0f16686 100644
--- a/chrome/browser/extensions/extensions_quota_service.h
+++ b/chrome/browser/extensions/extensions_quota_service.h
@@ -19,6 +19,7 @@
#include <map>
#include <string>
+#include "base/compiler_specific.h"
#include "base/hash_tables.h"
#include "base/memory/scoped_ptr.h"
#include "base/time.h"
@@ -141,6 +142,20 @@ class QuotaLimitHeuristic {
BucketList* buckets) = 0;
};
+ // Maps all calls to the same bucket, regardless of |args|, for this
+ // QuotaLimitHeuristic.
+ class SingletonBucketMapper : public BucketMapper {
+ public:
+ SingletonBucketMapper() {}
+ virtual ~SingletonBucketMapper() {}
+ virtual void GetBucketsForArgs(const ListValue* args,
+ BucketList* buckets) OVERRIDE;
+
+ private:
+ Bucket bucket_;
+ DISALLOW_COPY_AND_ASSIGN(SingletonBucketMapper);
+ };
+
// Ownership of |mapper| is given to the new QuotaLimitHeuristic.
QuotaLimitHeuristic(const Config& config, BucketMapper* map);
virtual ~QuotaLimitHeuristic();
diff --git a/chrome/renderer/resources/extensions/extension_process_bindings.js b/chrome/renderer/resources/extensions/extension_process_bindings.js
index f6d946e..96db368 100644
--- a/chrome/renderer/resources/extensions/extension_process_bindings.js
+++ b/chrome/renderer/resources/extensions/extension_process_bindings.js
@@ -933,6 +933,13 @@ var chrome = chrome || {};
{forIOThread: true});
};
+ apiFunctions["experimental.webRequest.handlerBehaviorChanged"].
+ handleRequest = function() {
+ var args = Array.prototype.slice.call(arguments);
+ sendRequest(this.name, args, this.definition.parameters,
+ {forIOThread: true});
+ };
+
apiFunctions["contextMenus.create"].customCallback =
function(name, request, response) {
if (chrome.extension.lastError) {