diff options
author | mek@chromium.org <mek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-14 21:01:06 +0000 |
---|---|---|
committer | mek@chromium.org <mek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-14 21:01:06 +0000 |
commit | 42774e4a2cffae20a5ab38ed43ed79ef9376e2fd (patch) | |
tree | 4e15852e85c196f8345b51595676d658fd8a1dbe | |
parent | 8116f33fd183f21a50a0da79ff9a8f1a9448754d (diff) | |
download | chromium_src-42774e4a2cffae20a5ab38ed43ed79ef9376e2fd.zip chromium_src-42774e4a2cffae20a5ab38ed43ed79ef9376e2fd.tar.gz chromium_src-42774e4a2cffae20a5ab38ed43ed79ef9376e2fd.tar.bz2 |
add chrome.runtime.requestUpdateCheck method.
To prevent bad-behaving extensions from potentially overloading the network, an extension is currently limited to checking for updates ones every 5 seconds.
TBR=jhawkins@chromium.org
BUG=88945
Review URL: https://chromiumcodereview.appspot.com/11339047
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@167741 0039d316-1c4b-4281-b951-d872f2087c98
15 files changed, 366 insertions, 82 deletions
diff --git a/chrome/browser/automation/testing_automation_provider.cc b/chrome/browser/automation/testing_automation_provider.cc index d0b2005..c36c16c 100644 --- a/chrome/browser/automation/testing_automation_provider.cc +++ b/chrome/browser/automation/testing_automation_provider.cc @@ -4145,8 +4145,10 @@ void TestingAutomationProvider::UpdateExtensionsNow( // been updated). This observer will delete itself. ExtensionsUpdatedObserver* observer = new ExtensionsUpdatedObserver( manager, this, reply_message); - updater->CheckNow(base::Bind(&ExtensionsUpdatedObserver::UpdateCheckFinished, - base::Unretained(observer))); + extensions::ExtensionUpdater::CheckParams params; + params.callback = base::Bind(&ExtensionsUpdatedObserver::UpdateCheckFinished, + base::Unretained(observer)); + updater->CheckNow(params); } #if !defined(NO_TCMALLOC) && (defined(OS_LINUX) || defined(OS_CHROMEOS)) diff --git a/chrome/browser/extensions/api/management/management_browsertest.cc b/chrome/browser/extensions/api/management/management_browsertest.cc index 54df1bd..1b9f9e3 100644 --- a/chrome/browser/extensions/api/management/management_browsertest.cc +++ b/chrome/browser/extensions/api/management/management_browsertest.cc @@ -13,6 +13,7 @@ #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/extension_test_message_listener.h" #include "chrome/browser/extensions/updater/extension_updater.h" +#include "chrome/browser/extensions/updater/extension_downloader.h" #include "chrome/browser/infobars/infobar_tab_helper.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/prefs/scoped_user_pref_update.h" @@ -202,9 +203,9 @@ class NotificationListener : public content::NotificationObserver { break; } case chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND: { - const std::string* id = - content::Details<const std::string>(details).ptr(); - updates_.insert(*id); + const std::string& id = + content::Details<extensions::UpdateDetails>(details)->id; + updates_.insert(id); break; } default: @@ -270,13 +271,15 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, MAYBE_AutoUpdate) { ASSERT_EQ("1.0", extension->VersionString()); // We don't want autoupdate blacklist checks. - service->updater()->set_blacklist_checks_enabled(false); + extensions::ExtensionUpdater::CheckParams params; + params.check_blacklist = false; + params.callback = + base::Bind(&NotificationListener::OnFinished, + base::Unretained(¬ification_listener)); // Run autoupdate and make sure version 2 of the extension was installed. ExtensionTestMessageListener listener2("v2 installed", false); - service->updater()->CheckNow( - base::Bind(&NotificationListener::OnFinished, - base::Unretained(¬ification_listener))); + service->updater()->CheckNow(params); ASSERT_TRUE(WaitForExtensionInstall()); listener2.WaitUntilSatisfied(); ASSERT_EQ(size_before + 1, service->extensions()->size()); @@ -297,9 +300,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, MAYBE_AutoUpdate) { interceptor->SetResponseOnIOThread("http://localhost/autoupdate/v3.crx", basedir.AppendASCII("v3.crx")); - service->updater()->CheckNow( - base::Bind(&NotificationListener::OnFinished, - base::Unretained(¬ification_listener))); + service->updater()->CheckNow(params); ASSERT_TRUE(WaitForExtensionInstallError()); ASSERT_TRUE(notification_listener.started()); ASSERT_TRUE(notification_listener.finished()); @@ -356,14 +357,16 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, ASSERT_EQ("1.0", extension->VersionString()); // We don't want autoupdate blacklist checks. - service->updater()->set_blacklist_checks_enabled(false); + extensions::ExtensionUpdater::CheckParams params; + params.check_blacklist = false; + params.callback = + base::Bind(&NotificationListener::OnFinished, + base::Unretained(¬ification_listener)); ExtensionTestMessageListener listener2("v2 installed", false); // Run autoupdate and make sure version 2 of the extension was installed but // is still disabled. - service->updater()->CheckNow( - base::Bind(&NotificationListener::OnFinished, - base::Unretained(¬ification_listener))); + service->updater()->CheckNow(params); ASSERT_TRUE(WaitForExtensionInstall()); ASSERT_EQ(disabled_size_before + 1, service->disabled_extensions()->size()); ASSERT_EQ(enabled_size_before, service->extensions()->size()); @@ -390,7 +393,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, ExternalUrlUpdate) { ExtensionService* service = browser()->profile()->GetExtensionService(); const char* kExtensionId = "ogjcoiohnmldgjemafoockdghcjciccf"; // We don't want autoupdate blacklist checks. - service->updater()->set_blacklist_checks_enabled(false); + extensions::ExtensionUpdater::CheckParams params; + params.check_blacklist = false; FilePath basedir = test_data_dir_.AppendASCII("autoupdate"); @@ -420,7 +424,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, ExternalUrlUpdate) { Extension::EXTERNAL_PREF_DOWNLOAD)); // Run autoupdate and make sure version 2 of the extension was installed. - service->updater()->CheckNow(base::Closure()); + service->updater()->CheckNow(params); ASSERT_TRUE(WaitForExtensionInstall()); ASSERT_EQ(size_before + 1, service->extensions()->size()); const Extension* extension = service->GetExtensionById(kExtensionId, false); @@ -474,7 +478,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, ExternalPolicyRefresh) { ExtensionService* service = browser()->profile()->GetExtensionService(); const char* kExtensionId = "ogjcoiohnmldgjemafoockdghcjciccf"; // We don't want autoupdate blacklist checks. - service->updater()->set_blacklist_checks_enabled(false); + extensions::ExtensionUpdater::CheckParams params; + params.check_blacklist = false; FilePath basedir = test_data_dir_.AppendASCII("autoupdate"); @@ -549,7 +554,9 @@ IN_PROC_BROWSER_TEST_F(ExtensionManagementTest, MAYBE_PolicyOverridesUserInstall) { ExtensionService* service = browser()->profile()->GetExtensionService(); const char* kExtensionId = "ogjcoiohnmldgjemafoockdghcjciccf"; - service->updater()->set_blacklist_checks_enabled(false); + extensions::ExtensionUpdater::CheckParams params; + params.check_blacklist = false; + service->updater()->set_default_check_params(params); const size_t size_before = service->extensions()->size(); FilePath basedir = test_data_dir_.AppendASCII("autoupdate"); ASSERT_TRUE(service->disabled_extensions()->is_empty()); diff --git a/chrome/browser/extensions/api/runtime/runtime_api.cc b/chrome/browser/extensions/api/runtime/runtime_api.cc index 1b15857..dfe9f4b 100644 --- a/chrome/browser/extensions/api/runtime/runtime_api.cc +++ b/chrome/browser/extensions/api/runtime/runtime_api.cc @@ -11,6 +11,7 @@ #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/lazy_background_task_queue.h" +#include "chrome/browser/extensions/updater/extension_updater.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/extensions/extension.h" @@ -30,6 +31,10 @@ const char kInstallReasonChromeUpdate[] = "chrome_update"; const char kInstallReasonUpdate[] = "update"; const char kInstallReasonInstall[] = "install"; const char kInstallPreviousVersion[] = "previousVersion"; +const char kUpdatesDisabledError[] = "Autoupdate is not enabled."; +const char kUpdateFound[] = "update_available"; +const char kUpdateNotFound[] = "no_update"; +const char kUpdateThrottled[] = "throttled"; static void DispatchOnStartupEventImpl( Profile* profile, @@ -166,4 +171,59 @@ bool RuntimeReloadFunction::RunImpl() { return true; } +RuntimeRequestUpdateCheckFunction::RuntimeRequestUpdateCheckFunction() { + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND, + content::NotificationService::AllSources()); +} + +bool RuntimeRequestUpdateCheckFunction::RunImpl() { + ExtensionSystem* system = ExtensionSystem::Get(profile()); + ExtensionService* service = system->extension_service(); + ExtensionUpdater* updater = service->updater(); + if (!updater) { + error_ = kUpdatesDisabledError; + return false; + } + + did_reply_ = false; + if (!updater->CheckExtensionSoon(extension_id(), base::Bind( + &RuntimeRequestUpdateCheckFunction::CheckComplete, this))) { + did_reply_ = true; + SetResult(new base::StringValue(kUpdateThrottled)); + SendResponse(true); + } + return true; +} + +void RuntimeRequestUpdateCheckFunction::CheckComplete() { + if (did_reply_) + return; + + did_reply_ = true; + SetResult(new base::StringValue(kUpdateNotFound)); + SendResponse(true); +} + +void RuntimeRequestUpdateCheckFunction::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (did_reply_) + return; + + DCHECK(type == chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND); + typedef const std::pair<std::string, Version> UpdateDetails; + const std::string& id = content::Details<UpdateDetails>(details)->first; + const Version& version = content::Details<UpdateDetails>(details)->second; + if (id == extension_id()) { + did_reply_ = true; + results_.reset(new base::ListValue); + results_->AppendString(kUpdateFound); + base::DictionaryValue* details = new base::DictionaryValue; + results_->Append(details); + details->SetString("version", version.GetString()); + SendResponse(true); + } +} + } // namespace extensions diff --git a/chrome/browser/extensions/api/runtime/runtime_api.h b/chrome/browser/extensions/api/runtime/runtime_api.h index 281cac9..b62b684 100644 --- a/chrome/browser/extensions/api/runtime/runtime_api.h +++ b/chrome/browser/extensions/api/runtime/runtime_api.h @@ -54,6 +54,27 @@ class RuntimeReloadFunction : public SyncExtensionFunction { virtual bool RunImpl() OVERRIDE; }; +class RuntimeRequestUpdateCheckFunction : public AsyncExtensionFunction, + public content::NotificationObserver { + public: + DECLARE_EXTENSION_FUNCTION_NAME("runtime.requestUpdateCheck"); + + RuntimeRequestUpdateCheckFunction(); + protected: + virtual ~RuntimeRequestUpdateCheckFunction() {} + virtual bool RunImpl() OVERRIDE; + + // Implements content::NotificationObserver interface. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + private: + void CheckComplete(); + + content::NotificationRegistrar registrar_; + bool did_reply_; +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_API_RUNTIME_RUNTIME_API_H_ diff --git a/chrome/browser/extensions/extension_function_registry.cc b/chrome/browser/extensions/extension_function_registry.cc index 7f5a5df..21b9030 100644 --- a/chrome/browser/extensions/extension_function_registry.cc +++ b/chrome/browser/extensions/extension_function_registry.cc @@ -515,6 +515,7 @@ void ExtensionFunctionRegistry::ResetFunctions() { // Runtime RegisterFunction<extensions::RuntimeGetBackgroundPageFunction>(); RegisterFunction<extensions::RuntimeReloadFunction>(); + RegisterFunction<extensions::RuntimeRequestUpdateCheckFunction>(); // Generated APIs extensions::api::GeneratedFunctionRegistry::RegisterAll(this); diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index bafe23c..9499f21 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -1763,7 +1763,7 @@ void ExtensionService::OnAllExternalProvidersReady() { // Install any pending extensions. if (update_once_all_providers_are_ready_ && updater()) { update_once_all_providers_are_ready_ = false; - updater()->CheckNow(base::Closure()); + updater()->CheckNow(extensions::ExtensionUpdater::CheckParams()); } // Uninstall all the unclaimed extensions. diff --git a/chrome/browser/extensions/updater/extension_downloader.cc b/chrome/browser/extensions/updater/extension_downloader.cc index e7e9160..8ca659f 100644 --- a/chrome/browser/extensions/updater/extension_downloader.cc +++ b/chrome/browser/extensions/updater/extension_downloader.cc @@ -102,6 +102,12 @@ void RecordCRXWriteHistogram(bool success, const FilePath& crx_path) { } // namespace +UpdateDetails::UpdateDetails(const std::string& id, const Version& version) + : id(id), version(version) {} + +UpdateDetails::~UpdateDetails() {} + + ExtensionDownloader::ExtensionFetch::ExtensionFetch() : id(""), url(), @@ -457,7 +463,7 @@ void ExtensionDownloader::HandleManifestResults( GURL crx_url = update->crx_url; if (id != kBlacklistAppID) { - NotifyUpdateFound(update->extension_id); + NotifyUpdateFound(update->extension_id, update->version); } else { // The URL of the blacklist file is returned by the server and we need to // be sure that we continue to be able to reliably detect whether a URL @@ -693,11 +699,13 @@ void ExtensionDownloader::NotifyExtensionsDownloadFailed( } } -void ExtensionDownloader::NotifyUpdateFound(const std::string& id) { +void ExtensionDownloader::NotifyUpdateFound(const std::string& id, + const std::string& version) { + UpdateDetails updateInfo(id, Version(version)); content::NotificationService::current()->Notify( chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND, content::NotificationService::AllBrowserContextsAndSources(), - content::Details<const std::string>(&id)); + content::Details<UpdateDetails>(&updateInfo)); } } // namespace extensions diff --git a/chrome/browser/extensions/updater/extension_downloader.h b/chrome/browser/extensions/updater/extension_downloader.h index cbc138c..8dd1331 100644 --- a/chrome/browser/extensions/updater/extension_downloader.h +++ b/chrome/browser/extensions/updater/extension_downloader.h @@ -16,6 +16,7 @@ #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" +#include "base/version.h" #include "chrome/browser/extensions/updater/extension_downloader_delegate.h" #include "chrome/browser/extensions/updater/manifest_fetch_data.h" #include "chrome/common/extensions/extension.h" @@ -33,6 +34,14 @@ class URLRequestStatus; namespace extensions { +struct UpdateDetails { + UpdateDetails(const std::string& id, const Version& version); + ~UpdateDetails(); + + std::string id; + Version version; +}; + class ExtensionUpdaterTest; // A class that checks for updates of a given list of extensions, and downloads @@ -174,7 +183,7 @@ class ExtensionDownloader : public net::URLFetcherDelegate { // Send a notification that an update was found for |id| that we'll // attempt to download. - void NotifyUpdateFound(const std::string& id); + void NotifyUpdateFound(const std::string& id, const std::string& version); // The delegate that receives the crx files downloaded by the // ExtensionDownloader, and that fills in optional ping and update url data. diff --git a/chrome/browser/extensions/updater/extension_updater.cc b/chrome/browser/extensions/updater/extension_updater.cc index e35f327..d26159d 100644 --- a/chrome/browser/extensions/updater/extension_updater.cc +++ b/chrome/browser/extensions/updater/extension_updater.cc @@ -53,6 +53,10 @@ const int kStartupWaitSeconds = 60 * 5; const int kMinUpdateFrequencySeconds = 30; const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7; // 7 days +// Require at least 5 seconds between consecutive non-succesful extension update +// checks. +const int kMinUpdateThrottleTime = 5; + // When we've computed a days value, we want to make sure we don't send a // negative value (due to the system clock being set backwards, etc.), since -1 // is a special sentinel value that means "never pinged", and other negative @@ -85,6 +89,11 @@ int CalculateActivePingDays(const Time& last_active_ping_day, namespace extensions { +ExtensionUpdater::CheckParams::CheckParams() + : check_blacklist(true) {} + +ExtensionUpdater::CheckParams::~CheckParams() {} + ExtensionUpdater::FetchedCRXFile::FetchedCRXFile( const std::string& i, const FilePath& p, @@ -107,6 +116,17 @@ ExtensionUpdater::InProgressCheck::InProgressCheck() ExtensionUpdater::InProgressCheck::~InProgressCheck() {} +struct ExtensionUpdater::ThrottleInfo { + ThrottleInfo() + : in_progress(true), + throttle_delay(kMinUpdateThrottleTime), + check_start(Time::Now()) {} + + bool in_progress; + int throttle_delay; + Time check_start; +}; + ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service, ExtensionPrefs* extension_prefs, PrefService* prefs, @@ -116,8 +136,7 @@ ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service, weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), service_(service), frequency_seconds_(frequency_seconds), will_check_soon_(false), extension_prefs_(extension_prefs), - prefs_(prefs), profile_(profile), blacklist_checks_enabled_(true), - next_request_id_(0), + prefs_(prefs), profile_(profile), next_request_id_(0), crx_install_is_running_(false) { DCHECK_GE(frequency_seconds_, 5); DCHECK_LE(frequency_seconds_, kMaxUpdateFrequencySeconds); @@ -126,6 +145,9 @@ ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service, frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds); #endif frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds); + + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, + content::NotificationService::AllBrowserContextsAndSources()); } ExtensionUpdater::~ExtensionUpdater() { @@ -224,7 +246,7 @@ void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) { void ExtensionUpdater::TimerFired() { DCHECK(alive_); - CheckNow(base::Closure()); + CheckNow(default_params_); // If the user has overridden the update frequency, don't bother reporting // this. @@ -267,7 +289,7 @@ bool ExtensionUpdater::WillCheckSoon() const { void ExtensionUpdater::DoCheckSoon() { DCHECK(will_check_soon_); - CheckNow(base::Closure()); + CheckNow(default_params_); will_check_soon_ = false; } @@ -294,16 +316,18 @@ void ExtensionUpdater::AddToDownloader( } } -void ExtensionUpdater::CheckNow(const FinishedCallback& callback) { +void ExtensionUpdater::CheckNow(const CheckParams& params) { int request_id = next_request_id_++; VLOG(2) << "Starting update check " << request_id; + if (params.ids.empty()) + NotifyStarted(); + DCHECK(alive_); - NotifyStarted(); InProgressCheck* request = &requests_in_progress_[request_id]; request->id = request_id; - request->callback = callback; + request->callback = params.callback; if (!downloader_.get()) { downloader_.reset( @@ -317,25 +341,38 @@ void ExtensionUpdater::CheckNow(const FinishedCallback& callback) { service_->pending_extension_manager(); std::list<std::string> pending_ids; - pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_ids); - - std::list<std::string>::const_iterator iter; - for (iter = pending_ids.begin(); iter != pending_ids.end(); ++iter) { - const PendingExtensionInfo* info = pending_extension_manager->GetById( - *iter); - if (!Extension::IsAutoUpdateableLocation(info->install_source())) { - VLOG(2) << "Extension " << *iter << " is not auto updateable"; - continue; + + if (params.ids.empty()) { + // If no extension ids are specified, check for updates for all extensions. + pending_extension_manager->GetPendingIdsForUpdateCheck(&pending_ids); + + std::list<std::string>::const_iterator iter; + for (iter = pending_ids.begin(); iter != pending_ids.end(); ++iter) { + const PendingExtensionInfo* info = pending_extension_manager->GetById( + *iter); + if (!Extension::IsAutoUpdateableLocation(info->install_source())) { + VLOG(2) << "Extension " << *iter << " is not auto updateable"; + continue; + } + if (downloader_->AddPendingExtension(*iter, info->update_url(), + request_id)) + request->in_progress_ids_.push_back(*iter); } - if (downloader_->AddPendingExtension(*iter, info->update_url(), request_id)) - request->in_progress_ids_.push_back(*iter); - } - AddToDownloader(service_->extensions(), pending_ids, request); - AddToDownloader(service_->disabled_extensions(), pending_ids, request); + AddToDownloader(service_->extensions(), pending_ids, request); + AddToDownloader(service_->disabled_extensions(), pending_ids, request); + } else { + for (std::list<std::string>::const_iterator it = params.ids.begin(); + it != params.ids.end(); ++it) { + const Extension* extension = service_->GetExtensionById(*it, true); + DCHECK(extension); + if (downloader_->AddExtension(*extension, request->id)) + request->in_progress_ids_.push_back(extension->id()); + } + } // Start a fetch of the blacklist if needed. - if (blacklist_checks_enabled_) { + if (params.check_blacklist) { ManifestFetchData::PingData ping_data; ping_data.rollcall_days = CalculatePingDays(extension_prefs_->BlacklistLastPingDay()); @@ -361,6 +398,59 @@ void ExtensionUpdater::CheckNow(const FinishedCallback& callback) { NotifyIfFinished(request_id); } +bool ExtensionUpdater::CheckExtensionSoon(const std::string& extension_id, + const FinishedCallback& callback) { + bool have_throttle_info = ContainsKey(throttle_info_, extension_id); + ThrottleInfo& info = throttle_info_[extension_id]; + if (have_throttle_info) { + // We already had a ThrottleInfo object for this extension, check if the + // update check request should be allowed. + + // If another check is in progress, don't start a new check. + if (info.in_progress) + return false; + + Time now = Time::Now(); + Time last = info.check_start; + // If somehow time moved back, we don't want to infinitely keep throttling. + if (now < last) { + last = now; + info.check_start = now; + } + Time earliest = last + TimeDelta::FromSeconds(info.throttle_delay); + // If check is too soon, throttle. + if (now < earliest) + return false; + + // TODO(mek): Somehow increase time between allowing checks when checks + // are repeatedly throttled and don't result in updates being installed. + + // It's okay to start a check, update values. + info.check_start = now; + info.in_progress = true; + } + + CheckParams params; + params.ids.push_back(extension_id); + params.check_blacklist = false; + params.callback = base::Bind(&ExtensionUpdater::ExtensionCheckFinished, + weak_ptr_factory_.GetWeakPtr(), + extension_id, callback); + CheckNow(params); + return true; +} + +void ExtensionUpdater::ExtensionCheckFinished( + const std::string& extension_id, + const FinishedCallback& callback) { + std::map<std::string, ThrottleInfo>::iterator it = + throttle_info_.find(extension_id); + if (it != throttle_info_.end()) { + it->second.in_progress = false; + } + callback.Run(); +} + void ExtensionUpdater::OnExtensionDownloadFailed( const std::string& id, Error error, @@ -532,23 +622,36 @@ void ExtensionUpdater::MaybeInstallCRXFile() { void ExtensionUpdater::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { - DCHECK(type == chrome::NOTIFICATION_CRX_INSTALLER_DONE); - - // No need to listen for CRX_INSTALLER_DONE anymore. - registrar_.Remove(this, - chrome::NOTIFICATION_CRX_INSTALLER_DONE, - source); - crx_install_is_running_ = false; - - const FetchedCRXFile& crx_file = current_crx_file_; - for (std::set<int>::const_iterator it = crx_file.request_ids.begin(); - it != crx_file.request_ids.end(); ++it) { - requests_in_progress_[*it].in_progress_ids_.remove(crx_file.extension_id); - NotifyIfFinished(*it); - } + switch (type) { + case chrome::NOTIFICATION_CRX_INSTALLER_DONE: { + // No need to listen for CRX_INSTALLER_DONE anymore. + registrar_.Remove(this, + chrome::NOTIFICATION_CRX_INSTALLER_DONE, + source); + crx_install_is_running_ = false; + + const FetchedCRXFile& crx_file = current_crx_file_; + for (std::set<int>::const_iterator it = crx_file.request_ids.begin(); + it != crx_file.request_ids.end(); ++it) { + InProgressCheck& request = requests_in_progress_[*it]; + request.in_progress_ids_.remove(crx_file.extension_id); + NotifyIfFinished(*it); + } - // If any files are available to update, start one. - MaybeInstallCRXFile(); + // If any files are available to update, start one. + MaybeInstallCRXFile(); + break; + } + case chrome::NOTIFICATION_EXTENSION_INSTALLED: { + const Extension* extension = + content::Details<const Extension>(details).ptr(); + if (extension) + throttle_info_.erase(extension->id()); + break; + } + default: + NOTREACHED(); + } } void ExtensionUpdater::NotifyStarted() { diff --git a/chrome/browser/extensions/updater/extension_updater.h b/chrome/browser/extensions/updater/extension_updater.h index 6ecfacf..ae59b7d 100644 --- a/chrome/browser/extensions/updater/extension_updater.h +++ b/chrome/browser/extensions/updater/extension_updater.h @@ -51,6 +51,24 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate, public: typedef base::Closure FinishedCallback; + struct CheckParams { + // Creates a default CheckParams instance that checks for all extensions and + // the extension blacklist. + CheckParams(); + ~CheckParams(); + + // The set of extensions that should be checked for updates. If empty + // all extensions will be included in the update check. + std::list<std::string> ids; + + // If true, the extension blacklist will also be updated. + bool check_blacklist; + + // Callback to call when the update check is complete. Can be null, if + // you're not interested in when this happens. + FinishedCallback callback; + }; + // Holds a pointer to the passed |service|, using it for querying installed // extensions and installing updated ones. The |frequency_seconds| parameter // controls how often update checks are scheduled. @@ -73,20 +91,27 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate, // already a pending task that has not yet run. void CheckSoon(); + // Starts an update check for the specified extension soon. If a check + // is already running, or finished too recently without an update being + // installed, this method returns false and the check won't be scheduled. + bool CheckExtensionSoon(const std::string& extension_id, + const FinishedCallback& callback); + // Starts an update check right now, instead of waiting for the next // regularly scheduled check or a pending check from CheckSoon(). - void CheckNow(const FinishedCallback& callback); - - // Set blacklist checks on or off. - void set_blacklist_checks_enabled(bool enabled) { - blacklist_checks_enabled_ = enabled; - } + void CheckNow(const CheckParams& params); // Returns true iff CheckSoon() has been called but the update check // hasn't been performed yet. This is used mostly by tests; calling // code should just call CheckSoon(). bool WillCheckSoon() const; + // Changes the params that are used for the automatic periodic update checks, + // as well as for explicit calls to CheckSoon. + void set_default_check_params(const CheckParams& params) { + default_params_ = params; + } + private: friend class ExtensionUpdaterTest; friend class ExtensionUpdaterFileHandler; @@ -117,6 +142,8 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate, std::list<std::string> in_progress_ids_; }; + struct ThrottleInfo; + // Computes when to schedule the first update check. base::TimeDelta DetermineFirstCheckDelay(); @@ -187,6 +214,9 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate, // Send a notification if we're finished updating. void NotifyIfFinished(int request_id); + void ExtensionCheckFinished(const std::string& extension_id, + const FinishedCallback& callback); + // Whether Start() has been called but not Stop(). bool alive_; @@ -205,7 +235,6 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate, ExtensionPrefs* extension_prefs_; PrefService* prefs_; Profile* profile_; - bool blacklist_checks_enabled_; std::map<int, InProgressCheck> requests_in_progress_; int next_request_id_; @@ -221,6 +250,12 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate, std::stack<FetchedCRXFile> fetched_crx_files_; FetchedCRXFile current_crx_file_; + CheckParams default_params_; + + // Keeps track of when an extension tried to update itself, so we can throttle + // checks to prevent too many requests from being made. + std::map<std::string, ThrottleInfo> throttle_info_; + DISALLOW_COPY_AND_ASSIGN(ExtensionUpdater); }; diff --git a/chrome/browser/extensions/updater/extension_updater_unittest.cc b/chrome/browser/extensions/updater/extension_updater_unittest.cc index d2bf559..4fd0346 100644 --- a/chrome/browser/extensions/updater/extension_updater_unittest.cc +++ b/chrome/browser/extensions/updater/extension_updater_unittest.cc @@ -144,7 +144,7 @@ class NotificationsObserver : public content::NotificationObserver { count_[i]++; if (type == chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND) { updated_.insert( - *(content::Details<const std::string>(details).ptr())); + content::Details<UpdateDetails>(details)->id); } return; } @@ -504,7 +504,9 @@ class ExtensionUpdaterTest : public testing::Test { updater.Start(); // Disable blacklist checks (tested elsewhere) so that we only see the // update HTTP request. - updater.set_blacklist_checks_enabled(false); + ExtensionUpdater::CheckParams check_params; + check_params.check_blacklist = false; + updater.set_default_check_params(check_params); // Tell the update that it's time to do update checks. SimulateTimerFired(&updater); @@ -1181,9 +1183,10 @@ class ExtensionUpdaterTest : public testing::Test { ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); - updater.set_blacklist_checks_enabled(false); + ExtensionUpdater::CheckParams params; + params.check_blacklist = false; updater.Start(); - updater.CheckNow(base::Closure()); + updater.CheckNow(params); // Make the updater do manifest fetching, and note the urls it tries to // fetch. @@ -1401,9 +1404,10 @@ TEST_F(ExtensionUpdaterTest, TestNonAutoUpdateableLocations) { EXPECT_CALL(delegate, GetPingDataForExtension(updateable_id, _)); service.set_extensions(extensions); - updater.set_blacklist_checks_enabled(false); + ExtensionUpdater::CheckParams params; + params.check_blacklist = false; updater.Start(); - updater.CheckNow(base::Closure()); + updater.CheckNow(params); } TEST_F(ExtensionUpdaterTest, TestUpdatingDisabledExtensions) { @@ -1439,9 +1443,10 @@ TEST_F(ExtensionUpdaterTest, TestUpdatingDisabledExtensions) { service.set_extensions(enabled_extensions); service.set_disabled_extensions(disabled_extensions); - updater.set_blacklist_checks_enabled(false); + ExtensionUpdater::CheckParams params; + params.check_blacklist = false; updater.Start(); - updater.CheckNow(base::Closure()); + updater.CheckNow(params); } TEST_F(ExtensionUpdaterTest, TestManifestFetchesBuilderAddExtension) { diff --git a/chrome/browser/first_run/first_run_win.cc b/chrome/browser/first_run/first_run_win.cc index 0d99c3a..4bd884b 100644 --- a/chrome/browser/first_run/first_run_win.cc +++ b/chrome/browser/first_run/first_run_win.cc @@ -98,7 +98,7 @@ class FirstRunDelayedTasks : public content::NotificationObserver { // extension it will get updated which is the same as get it installed. void DoExtensionWork(ExtensionService* service) { if (service) - service->updater()->CheckNow(base::Closure()); + service->updater()->CheckNow(extensions::ExtensionUpdater::CheckParams()); } content::NotificationRegistrar registrar_; diff --git a/chrome/browser/ui/webui/extensions/extension_settings_handler.cc b/chrome/browser/ui/webui/extensions/extension_settings_handler.cc index 1e3eb37..b59a21d 100644 --- a/chrome/browser/ui/webui/extensions/extension_settings_handler.cc +++ b/chrome/browser/ui/webui/extensions/extension_settings_handler.cc @@ -772,7 +772,7 @@ void ExtensionSettingsHandler::HandleShowButtonMessage(const ListValue* args) { void ExtensionSettingsHandler::HandleAutoUpdateMessage(const ListValue* args) { ExtensionUpdater* updater = extension_service_->updater(); if (updater) - updater->CheckNow(base::Closure()); + updater->CheckNow(extensions::ExtensionUpdater::CheckParams()); } void ExtensionSettingsHandler::HandleLoadUnpackedExtensionMessage( diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index df9509a..90e8f18 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -670,8 +670,9 @@ enum NotificationType { NOTIFICATION_EXTENSION_UPDATING_STARTED, // The extension updater found an update and will attempt to download and - // install it. The source is a Profile, and the details are an extension id - // (const std::string). + // install it. The source is a Profile, and the details are an + // extensions::UpdateDetails object with the extension id and version of the + // found update. NOTIFICATION_EXTENSION_UPDATE_FOUND, // An installed app changed notification state (added or removed diff --git a/chrome/common/extensions/api/runtime.json b/chrome/common/extensions/api/runtime.json index 36c024bd..fddd130 100644 --- a/chrome/common/extensions/api/runtime.json +++ b/chrome/common/extensions/api/runtime.json @@ -86,6 +86,38 @@ "type": "function", "unprivileged": true, "parameters": [] + }, + { + "name": "requestUpdateCheck", + "type": "function", + "unprivileged": true, + "description": "Requests an update check for this app/extension.", + "parameters": [ + { + "type": "function", + "name": "callback", + "parameters": [ + { + "name": "status", + "type": "string", + "enum": ["throttled", "no_update", "update_available"], + "description": "Result of the update check." + }, + { + "name": "details", + "type": "object", + "optional": true, + "properties": { + "version": { + "type": "string", + "description": "The version of the available update." + } + }, + "description": "If an update is available, this contains more information about the available update." + } + ] + } + ] } ], "events": [ |