This source file includes following definitions.
- ShouldRetryRequest
- version
- is_protected
- is_protected
- extension_cache_
- AddExtension
- AddPendingExtension
- StartAllPending
- DoStartAllPending
- StartBlacklistUpdate
- AddExtensionData
- ReportStats
- StartUpdateCheck
- CreateManifestFetcher
- OnURLFetchComplete
- OnManifestFetchComplete
- HandleManifestResults
- DetermineUpdates
- FetchUpdatedExtension
- NotifyDelegateDownloadFinished
- CreateExtensionFetcher
- OnCRXFetchComplete
- NotifyExtensionsDownloadFailed
- NotifyUpdateFound
#include "chrome/browser/extensions/updater/extension_downloader.h"
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/scoped_handle.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/platform_file.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "base/version.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/updater/extension_cache.h"
#include "chrome/browser/extensions/updater/request_queue_impl.h"
#include "chrome/browser/extensions/updater/safe_manifest_parser.h"
#include "chrome/browser/metrics/metrics_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/manifest_url_handler.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "net/base/backoff_entry.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
using base::Time;
using base::TimeDelta;
using content::BrowserThread;
namespace extensions {
const char ExtensionDownloader::kBlacklistAppID[] = "com.google.crx.blacklist";
namespace {
const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
0,
2000,
2,
0.1,
-1,
-1,
false,
};
const char kNotFromWebstoreInstallSource[] = "notfromwebstore";
const char kDefaultInstallSource[] = "";
#define RETRY_HISTOGRAM(name, retry_count, url) \
if ((url).DomainIs("google.com")) { \
UMA_HISTOGRAM_CUSTOM_COUNTS( \
"Extensions." name "RetryCountGoogleUrl", retry_count, 1, \
kMaxRetries, kMaxRetries+1); \
} else { \
UMA_HISTOGRAM_CUSTOM_COUNTS( \
"Extensions." name "RetryCountOtherUrl", retry_count, 1, \
kMaxRetries, kMaxRetries+1); \
}
bool ShouldRetryRequest(const net::URLRequestStatus& status,
int response_code) {
return (response_code >= 500 && status.is_success()) ||
status.status() == net::URLRequestStatus::FAILED;
}
}
UpdateDetails::UpdateDetails(const std::string& id, const Version& version)
: id(id), version(version) {}
UpdateDetails::~UpdateDetails() {}
ExtensionDownloader::ExtensionFetch::ExtensionFetch()
: url(), is_protected(false) {}
ExtensionDownloader::ExtensionFetch::ExtensionFetch(
const std::string& id,
const GURL& url,
const std::string& package_hash,
const std::string& version,
const std::set<int>& request_ids)
: id(id), url(url), package_hash(package_hash), version(version),
request_ids(request_ids), is_protected(false) {}
ExtensionDownloader::ExtensionFetch::~ExtensionFetch() {}
ExtensionDownloader::ExtensionDownloader(
ExtensionDownloaderDelegate* delegate,
net::URLRequestContextGetter* request_context)
: delegate_(delegate),
request_context_(request_context),
weak_ptr_factory_(this),
manifests_queue_(&kDefaultBackoffPolicy,
base::Bind(&ExtensionDownloader::CreateManifestFetcher,
base::Unretained(this))),
extensions_queue_(&kDefaultBackoffPolicy,
base::Bind(&ExtensionDownloader::CreateExtensionFetcher,
base::Unretained(this))),
extension_cache_(NULL) {
DCHECK(delegate_);
DCHECK(request_context_);
}
ExtensionDownloader::~ExtensionDownloader() {}
bool ExtensionDownloader::AddExtension(const Extension& extension,
int request_id) {
if (extension.converted_from_user_script() &&
ManifestURL::GetUpdateURL(&extension).is_empty()) {
return false;
}
std::string update_url_data;
if (!ManifestURL::UpdatesFromGallery(&extension))
update_url_data = delegate_->GetUpdateUrlData(extension.id());
return AddExtensionData(extension.id(), *extension.version(),
extension.GetType(),
ManifestURL::GetUpdateURL(&extension),
update_url_data, request_id);
}
bool ExtensionDownloader::AddPendingExtension(const std::string& id,
const GURL& update_url,
int request_id) {
Version version("0.0.0.0");
DCHECK(version.IsValid());
return AddExtensionData(id,
version,
Manifest::TYPE_UNKNOWN,
update_url,
std::string(),
request_id);
}
void ExtensionDownloader::StartAllPending(ExtensionCache* cache) {
if (cache) {
extension_cache_ = cache;
extension_cache_->Start(base::Bind(
&ExtensionDownloader::DoStartAllPending,
weak_ptr_factory_.GetWeakPtr()));
} else {
DoStartAllPending();
}
}
void ExtensionDownloader::DoStartAllPending() {
ReportStats();
url_stats_ = URLStats();
for (FetchMap::iterator it = fetches_preparing_.begin();
it != fetches_preparing_.end(); ++it) {
std::vector<linked_ptr<ManifestFetchData> >& list = it->second;
for (size_t i = 0; i < list.size(); ++i) {
StartUpdateCheck(scoped_ptr<ManifestFetchData>(list[i].release()));
}
}
fetches_preparing_.clear();
}
void ExtensionDownloader::StartBlacklistUpdate(
const std::string& version,
const ManifestFetchData::PingData& ping_data,
int request_id) {
scoped_ptr<ManifestFetchData> blacklist_fetch(
new ManifestFetchData(extension_urls::GetWebstoreUpdateUrl(),
request_id));
DCHECK(blacklist_fetch->base_url().SchemeIsSecure());
blacklist_fetch->AddExtension(kBlacklistAppID,
version,
&ping_data,
std::string(),
kDefaultInstallSource);
StartUpdateCheck(blacklist_fetch.Pass());
}
bool ExtensionDownloader::AddExtensionData(const std::string& id,
const Version& version,
Manifest::Type extension_type,
const GURL& extension_update_url,
const std::string& update_url_data,
int request_id) {
GURL update_url(extension_update_url);
if (!update_url.is_empty() && !update_url.is_valid()) {
LOG(WARNING) << "Extension " << id << " has invalid update url "
<< update_url;
return false;
}
if (extension_urls::IsWebstoreUpdateUrl(update_url) &&
!update_url.SchemeIsSecure())
update_url = extension_urls::GetWebstoreUpdateUrl();
if (id.empty()) {
LOG(WARNING) << "Found extension with empty ID";
return false;
}
if (update_url.DomainIs("google.com")) {
url_stats_.google_url_count++;
} else if (update_url.is_empty()) {
url_stats_.no_url_count++;
update_url = extension_urls::GetWebstoreUpdateUrl();
} else {
url_stats_.other_url_count++;
}
switch (extension_type) {
case Manifest::TYPE_THEME:
++url_stats_.theme_count;
break;
case Manifest::TYPE_EXTENSION:
case Manifest::TYPE_USER_SCRIPT:
++url_stats_.extension_count;
break;
case Manifest::TYPE_HOSTED_APP:
case Manifest::TYPE_LEGACY_PACKAGED_APP:
++url_stats_.app_count;
break;
case Manifest::TYPE_PLATFORM_APP:
++url_stats_.platform_app_count;
break;
case Manifest::TYPE_UNKNOWN:
default:
++url_stats_.pending_count;
break;
}
std::vector<GURL> update_urls;
update_urls.push_back(update_url);
if (!extension_urls::IsWebstoreUpdateUrl(update_url) &&
MetricsServiceHelper::IsMetricsReportingEnabled()) {
update_urls.push_back(extension_urls::GetWebstoreUpdateUrl());
}
for (size_t i = 0; i < update_urls.size(); ++i) {
DCHECK(!update_urls[i].is_empty());
DCHECK(update_urls[i].is_valid());
std::string install_source = i == 0 ?
kDefaultInstallSource : kNotFromWebstoreInstallSource;
ManifestFetchData::PingData ping_data;
ManifestFetchData::PingData* optional_ping_data = NULL;
if (delegate_->GetPingDataForExtension(id, &ping_data))
optional_ping_data = &ping_data;
bool added = false;
FetchMap::iterator existing_iter = fetches_preparing_.find(
std::make_pair(request_id, update_urls[i]));
if (existing_iter != fetches_preparing_.end() &&
!existing_iter->second.empty()) {
ManifestFetchData* existing_fetch = existing_iter->second.back().get();
if (existing_fetch->AddExtension(id, version.GetString(),
optional_ping_data, update_url_data,
install_source)) {
added = true;
}
}
if (!added) {
linked_ptr<ManifestFetchData> fetch(
new ManifestFetchData(update_urls[i], request_id));
fetches_preparing_[std::make_pair(request_id, update_urls[i])].
push_back(fetch);
added = fetch->AddExtension(id, version.GetString(),
optional_ping_data,
update_url_data,
install_source);
DCHECK(added);
}
}
return true;
}
void ExtensionDownloader::ReportStats() const {
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension",
url_stats_.extension_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme",
url_stats_.theme_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp",
url_stats_.app_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPackagedApp",
url_stats_.platform_app_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending",
url_stats_.pending_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl",
url_stats_.google_url_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl",
url_stats_.other_url_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl",
url_stats_.no_url_count);
}
void ExtensionDownloader::StartUpdateCheck(
scoped_ptr<ManifestFetchData> fetch_data) {
const std::set<std::string>& id_set(fetch_data->extension_ids());
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableBackgroundNetworking)) {
NotifyExtensionsDownloadFailed(id_set,
fetch_data->request_ids(),
ExtensionDownloaderDelegate::DISABLED);
return;
}
RequestQueue<ManifestFetchData>::iterator i;
for (i = manifests_queue_.begin(); i != manifests_queue_.end(); ++i) {
if (fetch_data->full_url() == i->full_url()) {
i->Merge(*fetch_data);
return;
}
}
if (manifests_queue_.active_request() &&
manifests_queue_.active_request()->full_url() == fetch_data->full_url()) {
manifests_queue_.active_request()->Merge(*fetch_data);
} else {
UMA_HISTOGRAM_COUNTS("Extensions.UpdateCheckUrlLength",
fetch_data->full_url().possibly_invalid_spec().length());
manifests_queue_.ScheduleRequest(fetch_data.Pass());
}
}
void ExtensionDownloader::CreateManifestFetcher() {
if (VLOG_IS_ON(2)) {
std::vector<std::string> id_vector(
manifests_queue_.active_request()->extension_ids().begin(),
manifests_queue_.active_request()->extension_ids().end());
std::string id_list = JoinString(id_vector, ',');
VLOG(2) << "Fetching " << manifests_queue_.active_request()->full_url()
<< " for " << id_list;
}
manifest_fetcher_.reset(net::URLFetcher::Create(
kManifestFetcherId, manifests_queue_.active_request()->full_url(),
net::URLFetcher::GET, this));
manifest_fetcher_->SetRequestContext(request_context_);
manifest_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DISABLE_CACHE);
manifest_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
manifest_fetcher_->Start();
}
void ExtensionDownloader::OnURLFetchComplete(
const net::URLFetcher* source) {
VLOG(2) << source->GetResponseCode() << " " << source->GetURL();
if (source == manifest_fetcher_.get()) {
std::string data;
source->GetResponseAsString(&data);
OnManifestFetchComplete(source->GetURL(),
source->GetStatus(),
source->GetResponseCode(),
source->GetBackoffDelay(),
data);
} else if (source == extension_fetcher_.get()) {
OnCRXFetchComplete(source,
source->GetURL(),
source->GetStatus(),
source->GetResponseCode(),
source->GetBackoffDelay());
} else {
NOTREACHED();
}
}
void ExtensionDownloader::OnManifestFetchComplete(
const GURL& url,
const net::URLRequestStatus& status,
int response_code,
const base::TimeDelta& backoff_delay,
const std::string& data) {
if (status.status() == net::URLRequestStatus::SUCCESS &&
(response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) {
RETRY_HISTOGRAM("ManifestFetchSuccess",
manifests_queue_.active_request_failure_count(), url);
VLOG(2) << "beginning manifest parse for " << url;
scoped_refptr<SafeManifestParser> safe_parser(
new SafeManifestParser(
data,
manifests_queue_.reset_active_request().release(),
base::Bind(&ExtensionDownloader::HandleManifestResults,
weak_ptr_factory_.GetWeakPtr())));
safe_parser->Start();
} else {
VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
if (ShouldRetryRequest(status, response_code) &&
manifests_queue_.active_request_failure_count() < kMaxRetries) {
manifests_queue_.RetryRequest(backoff_delay);
} else {
RETRY_HISTOGRAM("ManifestFetchFailure",
manifests_queue_.active_request_failure_count(), url);
NotifyExtensionsDownloadFailed(
manifests_queue_.active_request()->extension_ids(),
manifests_queue_.active_request()->request_ids(),
ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED);
}
}
manifest_fetcher_.reset();
manifests_queue_.reset_active_request();
manifests_queue_.StartNextRequest();
}
void ExtensionDownloader::HandleManifestResults(
const ManifestFetchData& fetch_data,
const UpdateManifest::Results* results) {
std::set<std::string> not_updated(fetch_data.extension_ids());
if (!results) {
NotifyExtensionsDownloadFailed(
not_updated,
fetch_data.request_ids(),
ExtensionDownloaderDelegate::MANIFEST_INVALID);
return;
}
std::vector<int> updates;
DetermineUpdates(fetch_data, *results, &updates);
for (size_t i = 0; i < updates.size(); i++) {
const UpdateManifest::Result* update = &(results->list.at(updates[i]));
const std::string& id = update->extension_id;
not_updated.erase(id);
GURL crx_url = update->crx_url;
if (id != kBlacklistAppID) {
NotifyUpdateFound(update->extension_id, update->version);
} else {
DCHECK(extension_urls::IsBlacklistUpdateUrl(crx_url)) << crx_url;
if (!crx_url.SchemeIsSecure()) {
url_canon::Replacements<char> replacements;
std::string scheme("https");
replacements.SetScheme(scheme.c_str(),
url_parse::Component(0, scheme.size()));
crx_url = crx_url.ReplaceComponents(replacements);
}
}
scoped_ptr<ExtensionFetch> fetch(new ExtensionFetch(
update->extension_id, crx_url, update->package_hash,
update->version, fetch_data.request_ids()));
FetchUpdatedExtension(fetch.Pass());
}
if (fetch_data.base_url().DomainIs("google.com") &&
results->daystart_elapsed_seconds >= 0) {
Time day_start =
Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds);
const std::set<std::string>& extension_ids = fetch_data.extension_ids();
std::set<std::string>::const_iterator i;
for (i = extension_ids.begin(); i != extension_ids.end(); i++) {
const std::string& id = *i;
ExtensionDownloaderDelegate::PingResult& result = ping_results_[id];
result.did_ping = fetch_data.DidPing(id, ManifestFetchData::ROLLCALL);
result.day_start = day_start;
}
}
NotifyExtensionsDownloadFailed(
not_updated,
fetch_data.request_ids(),
ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE);
}
void ExtensionDownloader::DetermineUpdates(
const ManifestFetchData& fetch_data,
const UpdateManifest::Results& possible_updates,
std::vector<int>* result) {
Version browser_version;
for (size_t i = 0; i < possible_updates.list.size(); i++) {
const UpdateManifest::Result* update = &possible_updates.list[i];
const std::string& id = update->extension_id;
if (!fetch_data.Includes(id)) {
VLOG(2) << "Ignoring " << id << " from this manifest";
continue;
}
if (VLOG_IS_ON(2)) {
if (update->version.empty())
VLOG(2) << "manifest indicates " << id << " has no update";
else
VLOG(2) << "manifest indicates " << id
<< " latest version is '" << update->version << "'";
}
if (!delegate_->IsExtensionPending(id)) {
std::string version;
if (!delegate_->GetExtensionExistingVersion(id, &version)) {
VLOG(2) << id << " is not installed";
continue;
}
VLOG(2) << id << " is at '" << version << "'";
Version existing_version(version);
Version update_version(update->version);
if (!update_version.IsValid() ||
update_version.CompareTo(existing_version) <= 0) {
continue;
}
}
if (update->browser_min_version.length() > 0) {
if (!browser_version.IsValid()) {
chrome::VersionInfo version_info;
if (version_info.is_valid())
browser_version = Version(version_info.Version());
}
Version browser_min_version(update->browser_min_version);
if (browser_version.IsValid() && browser_min_version.IsValid() &&
browser_min_version.CompareTo(browser_version) > 0) {
LOG(WARNING) << "Updated version of extension " << id
<< " available, but requires chrome version "
<< update->browser_min_version;
continue;
}
}
VLOG(2) << "will try to update " << id;
result->push_back(i);
}
}
void ExtensionDownloader::FetchUpdatedExtension(
scoped_ptr<ExtensionFetch> fetch_data) {
if (!fetch_data->url.is_valid()) {
LOG(ERROR) << "Invalid URL: '" << fetch_data->url.possibly_invalid_spec()
<< "' for extension " << fetch_data->id;
return;
}
for (RequestQueue<ExtensionFetch>::iterator iter =
extensions_queue_.begin();
iter != extensions_queue_.end(); ++iter) {
if (iter->id == fetch_data->id || iter->url == fetch_data->url) {
iter->request_ids.insert(fetch_data->request_ids.begin(),
fetch_data->request_ids.end());
return;
}
}
if (extensions_queue_.active_request() &&
extensions_queue_.active_request()->url == fetch_data->url) {
extensions_queue_.active_request()->request_ids.insert(
fetch_data->request_ids.begin(), fetch_data->request_ids.end());
} else {
std::string version;
if (extension_cache_ &&
extension_cache_->GetExtension(fetch_data->id, NULL, &version) &&
version == fetch_data->version) {
base::FilePath crx_path;
extension_cache_->GetExtension(fetch_data->id, &crx_path, &version);
NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, false);
} else {
extensions_queue_.ScheduleRequest(fetch_data.Pass());
}
}
}
void ExtensionDownloader::NotifyDelegateDownloadFinished(
scoped_ptr<ExtensionFetch> fetch_data,
const base::FilePath& crx_path,
bool file_ownership_passed) {
delegate_->OnExtensionDownloadFinished(fetch_data->id, crx_path,
file_ownership_passed, fetch_data->url, fetch_data->version,
ping_results_[fetch_data->id], fetch_data->request_ids);
ping_results_.erase(fetch_data->id);
}
void ExtensionDownloader::CreateExtensionFetcher() {
const ExtensionFetch* fetch = extensions_queue_.active_request();
int load_flags = net::LOAD_DISABLE_CACHE;
if (!fetch->is_protected || !fetch->url.SchemeIs("https")) {
load_flags |= net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES;
}
extension_fetcher_.reset(net::URLFetcher::Create(
kExtensionFetcherId, fetch->url, net::URLFetcher::GET, this));
extension_fetcher_->SetRequestContext(request_context_);
extension_fetcher_->SetLoadFlags(load_flags);
extension_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
if (fetch->id != kBlacklistAppID) {
extension_fetcher_->SaveResponseToTemporaryFile(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
}
VLOG(2) << "Starting fetch of " << fetch->url << " for " << fetch->id;
extension_fetcher_->Start();
}
void ExtensionDownloader::OnCRXFetchComplete(
const net::URLFetcher* source,
const GURL& url,
const net::URLRequestStatus& status,
int response_code,
const base::TimeDelta& backoff_delay) {
const std::string& id = extensions_queue_.active_request()->id;
if (status.status() == net::URLRequestStatus::SUCCESS &&
(response_code == 200 || url.SchemeIsFile())) {
RETRY_HISTOGRAM("CrxFetchSuccess",
extensions_queue_.active_request_failure_count(), url);
base::FilePath crx_path;
CHECK(source->GetResponseAsFilePath(true, &crx_path));
scoped_ptr<ExtensionFetch> fetch_data =
extensions_queue_.reset_active_request();
if (extension_cache_) {
const std::string& version = fetch_data->version;
extension_cache_->PutExtension(id, crx_path, version,
base::Bind(&ExtensionDownloader::NotifyDelegateDownloadFinished,
weak_ptr_factory_.GetWeakPtr(),
base::Passed(&fetch_data)));
} else {
NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, true);
}
} else if (status.status() == net::URLRequestStatus::SUCCESS &&
(response_code == 401 || response_code == 403) &&
!extensions_queue_.active_request()->is_protected) {
extensions_queue_.active_request()->is_protected = true;
extensions_queue_.RetryRequest(backoff_delay);
} else {
const std::set<int>& request_ids =
extensions_queue_.active_request()->request_ids;
const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id];
VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
if (ShouldRetryRequest(status, response_code) &&
extensions_queue_.active_request_failure_count() < kMaxRetries) {
extensions_queue_.RetryRequest(backoff_delay);
} else {
RETRY_HISTOGRAM("CrxFetchFailure",
extensions_queue_.active_request_failure_count(), url);
UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.CrxFetchError", -status.error());
delegate_->OnExtensionDownloadFailed(
id, ExtensionDownloaderDelegate::CRX_FETCH_FAILED, ping, request_ids);
}
ping_results_.erase(id);
extensions_queue_.reset_active_request();
}
extension_fetcher_.reset();
extensions_queue_.StartNextRequest();
}
void ExtensionDownloader::NotifyExtensionsDownloadFailed(
const std::set<std::string>& extension_ids,
const std::set<int>& request_ids,
ExtensionDownloaderDelegate::Error error) {
for (std::set<std::string>::const_iterator it = extension_ids.begin();
it != extension_ids.end(); ++it) {
const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[*it];
delegate_->OnExtensionDownloadFailed(*it, error, ping, request_ids);
ping_results_.erase(*it);
}
}
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<UpdateDetails>(&updateInfo));
}
}