diff options
author | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-03 09:20:25 +0000 |
---|---|---|
committer | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-03 09:20:25 +0000 |
commit | fd50e7b218e48d2299664f71b3af13e4acae1868 (patch) | |
tree | 6e0a1b640147e352b2d9849fcd47148dc5cc5d9e | |
parent | 52447df4674e60c7dc78fa6742885f8ae470a517 (diff) | |
download | chromium_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
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, ¶ms.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 "a_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) { |