Chromium Code Reviews| Index: chrome/browser/extensions/api/downloads/downloads_api.cc |
| diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc |
| index 8545f7dbef8a66997d43b3f68b969dc17f224c1d..9e76857c15047c1aa3e654b03027eb6534226199 100644 |
| --- a/chrome/browser/extensions/api/downloads/downloads_api.cc |
| +++ b/chrome/browser/extensions/api/downloads/downloads_api.cc |
| @@ -14,6 +14,7 @@ |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| +#include "base/file_path.h" |
| #include "base/json/json_writer.h" |
| #include "base/lazy_instance.h" |
| #include "base/logging.h" |
| @@ -23,6 +24,7 @@ |
| #include "base/string_split.h" |
| #include "base/string_util.h" |
| #include "base/stringprintf.h" |
| +#include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/download/download_danger_prompt.h" |
| @@ -34,6 +36,9 @@ |
| #include "chrome/browser/extensions/event_names.h" |
| #include "chrome/browser/extensions/event_router.h" |
| #include "chrome/browser/extensions/extension_function_dispatcher.h" |
| +#include "chrome/browser/extensions/extension_info_map.h" |
| +#include "chrome/browser/extensions/extension_prefs.h" |
| +#include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/extension_system.h" |
| #include "chrome/browser/icon_loader.h" |
| #include "chrome/browser/icon_manager.h" |
| @@ -185,14 +190,12 @@ DownloadItem::DownloadState StateEnumFromString(const std::string& state) { |
| return DownloadItem::MAX_DOWNLOAD_STATE; |
| } |
| -bool ValidateFilename(const string16& filename) { |
| - // TODO(benjhayden): More robust validation of filename. |
| - if ((filename.find('/') != string16::npos) || |
| - (filename.find('\\') != string16::npos)) |
| - return false; |
| - if (filename.size() >= 2u && filename[0] == L'.' && filename[1] == L'.') |
| - return false; |
| - return true; |
| +bool ValidateFilename(const base::FilePath& filename) { |
| + return !filename.empty() && |
| + (filename == filename.StripTrailingSeparators()) && |
| + (filename.BaseName().value() != base::FilePath::kCurrentDirectory) && |
| + !filename.ReferencesParent() && |
| + !filename.IsAbsolute(); |
| } |
| std::string TimeToISO8601(const base::Time& t) { |
| @@ -264,8 +267,7 @@ bool DownloadFileIconExtractorImpl::ExtractIconURLForPath( |
| IconManager* im = g_browser_process->icon_manager(); |
| // The contents of the file at |path| may have changed since a previous |
| // request, in which case the associated icon may also have changed. |
| - // Therefore, for the moment we always call LoadIcon instead of attempting |
| - // a LookupIcon. |
| + // Therefore, always call LoadIcon instead of attempting a LookupIcon. |
| im->LoadIcon(path, |
| icon_size, |
| base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete, |
| @@ -411,7 +413,7 @@ void CompileDownloadQueryOrderBy( |
| std::vector<std::string> order_by_strs; |
| base::SplitString(order_by_str, ' ', &order_by_strs); |
| for (std::vector<std::string>::const_iterator iter = order_by_strs.begin(); |
| - iter != order_by_strs.end(); ++iter) { |
| + iter != order_by_strs.end(); ++iter) { |
| std::string term_str = *iter; |
| if (term_str.empty()) |
| continue; |
| @@ -508,31 +510,6 @@ void RunDownloadQuery( |
| query_out.Search(all_items.begin(), all_items.end(), results); |
| } |
| -void DispatchEventInternal( |
| - Profile* target_profile, |
| - const char* event_name, |
| - const std::string& json_args, |
| - scoped_ptr<base::ListValue> event_args) { |
| - if (!extensions::ExtensionSystem::Get(target_profile)->event_router()) |
| - return; |
| - scoped_ptr<extensions::Event> event(new extensions::Event( |
| - event_name, event_args.Pass())); |
| - event->restrict_to_profile = target_profile; |
| - extensions::ExtensionSystem::Get(target_profile)->event_router()-> |
| - BroadcastEvent(event.Pass()); |
| - ExtensionDownloadsEventRouter::DownloadsNotificationSource |
| - notification_source; |
| - notification_source.event_name = event_name; |
| - notification_source.profile = target_profile; |
| - content::Source<ExtensionDownloadsEventRouter::DownloadsNotificationSource> |
| - content_source(¬ification_source); |
| - std::string args_copy(json_args); |
| - content::NotificationService::current()->Notify( |
| - chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, |
| - content_source, |
| - content::Details<std::string>(&args_copy)); |
| -} |
| - |
| class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data { |
| public: |
| static ExtensionDownloadsEventRouterData* Get(DownloadItem* download_item) { |
| @@ -546,7 +523,8 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data { |
| scoped_ptr<base::DictionaryValue> json_item) |
| : updated_(0), |
| changed_fired_(0), |
| - json_(json_item.Pass()) { |
| + json_(json_item.Pass()), |
| + determined_overwrite_(false) { |
| download_item->SetUserData(kKey, this); |
| } |
| @@ -565,16 +543,162 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data { |
| void OnItemUpdated() { ++updated_; } |
| void OnChangedFired() { ++changed_fired_; } |
| + void set_filename_change_callbacks( |
| + const base::Closure& no_change, |
| + const ExtensionDownloadsEventRouter::FilenameChangedCallback& change) { |
| + filename_no_change_ = no_change; |
| + filename_change_ = change; |
| + } |
| + |
| + void ClearPendingDeterminers() { |
| + determiners_.clear(); |
| + determined_filename_.clear(); |
| + determined_overwrite_ = false; |
| + determiner_ = DeterminerInfo(); |
| + filename_no_change_ = base::Closure(); |
| + filename_change_ = ExtensionDownloadsEventRouter::FilenameChangedCallback(); |
| + } |
| + |
| + void ExtensionUnloaded(const std::string& extension_id) { |
| + // Remove this extension from |determiners_|. |
| + for (DeterminerInfoVector::iterator iter = determiners_.begin(); |
| + iter != determiners_.end();) { |
| + if (iter->extension_id == extension_id) { |
| + iter = determiners_.erase(iter); |
| + } else { |
| + ++iter; |
| + } |
| + } |
| + // If we just removed the last unreported determiner, then we need to call a |
| + // callback. |
| + CheckAllDeterminersCalled(); |
| + } |
| + |
| + void DeterminerRemoved(const std::string& extension_id) { |
|
Matt Perry
2013/02/27 22:33:47
This is now the same as ExtensionUnloaded.
benjhayden
2013/03/01 20:42:21
Done.
|
| + for (DeterminerInfoVector::iterator iter = determiners_.begin(); |
| + iter != determiners_.end();) { |
| + if (iter->extension_id == extension_id) { |
| + iter = determiners_.erase(iter); |
| + } else { |
| + ++iter; |
| + } |
| + } |
| + // If we just removed the last unreported determiner, then we need to call a |
| + // callback. |
| + CheckAllDeterminersCalled(); |
| + } |
| + |
| + void AddPendingDeterminer(const std::string& extension_id, |
| + const base::Time& installed) { |
| + for (int index = 0; index < static_cast<int>(determiners_.size()); |
|
Matt Perry
2013/02/27 22:33:47
nit: use size_t index and avoid the case
benjhayden
2013/03/01 20:42:21
Done.
|
| + ++index) { |
| + if (determiners_[index].extension_id == extension_id) { |
| + DCHECK(false) << extension_id; |
| + return; |
| + } |
| + } |
| + determiners_.push_back(DeterminerInfo(extension_id, installed)); |
| + } |
| + |
| + bool DeterminerCallback( |
| + const std::string& extension_id, |
| + const base::FilePath& filename, |
| + bool overwrite) { |
| + bool filename_valid = ValidateFilename(filename); |
| + bool found_info = false; |
| + for (int index = 0; index < static_cast<int>(determiners_.size()); |
|
Matt Perry
2013/02/27 22:33:47
ditto
benjhayden
2013/03/01 20:42:21
Done.
|
| + ++index) { |
| + if (determiners_[index].extension_id == extension_id) { |
| + found_info = true; |
| + // maxListeners=1 in downloads.idl and suggestCallback in |
|
Matt Perry
2013/02/27 22:33:47
Does maxListeners work across pages? E.g. if an ex
benjhayden
2013/03/01 20:42:21
No and yes.
Added DeterminerAlreadyReported() and
|
| + // downloads_custom_bindings.js should prevent duplicate |
| + // DeterminerCallback calls. |
| + DCHECK(!determiners_[index].reported); |
| + if (determiners_[index].reported) |
| + return false; |
| + determiners_[index].reported = true; |
| + // Do not use filename if another determiner has already overridden the |
| + // filename and they take precedence. Extensions that were installed |
| + // later take precedence over previous extensions. |
| + if (filename_valid && |
| + (determiner_.extension_id.empty() || |
| + (determiners_[index].install_time > determiner_.install_time))) { |
| + determined_filename_ = filename; |
| + determined_overwrite_ = overwrite; |
| + determiner_ = determiners_[index]; |
| + } |
| + break; |
| + } |
| + } |
| + if (!found_info) |
| + return false; |
| + CheckAllDeterminersCalled(); |
| + return filename.empty() || filename_valid; |
| + } |
| + |
| private: |
| + struct DeterminerInfo { |
| + DeterminerInfo(); |
| + DeterminerInfo(const std::string& e_id, |
| + const base::Time& installed); |
| + ~DeterminerInfo(); |
| + |
| + std::string extension_id; |
| + base::Time install_time; |
| + bool reported; |
| + }; |
| + typedef std::vector<DeterminerInfo> DeterminerInfoVector; |
| + |
| static const char kKey[]; |
| + // This is safe to call even while not waiting for determiners to call back; |
| + // in that case, the callbacks will be null so they won't be Run. |
| + void CheckAllDeterminersCalled() { |
| + for (DeterminerInfoVector::iterator iter = determiners_.begin(); |
| + iter != determiners_.end(); ++iter) { |
| + if (!iter->reported) |
| + return; |
| + } |
| + if (determined_filename_.empty()) { |
| + if (!filename_no_change_.is_null()) |
| + filename_no_change_.Run(); |
| + } else { |
| + if (!filename_change_.is_null()) |
| + filename_change_.Run(determined_filename_, determined_overwrite_); |
| + } |
| + ClearPendingDeterminers(); |
| + } |
| + |
| int updated_; |
| int changed_fired_; |
| scoped_ptr<base::DictionaryValue> json_; |
| + base::Closure filename_no_change_; |
| + ExtensionDownloadsEventRouter::FilenameChangedCallback filename_change_; |
| + |
| + DeterminerInfoVector determiners_; |
| + |
| + base::FilePath determined_filename_; |
| + bool determined_overwrite_; |
| + DeterminerInfo determiner_; |
| + |
| DISALLOW_COPY_AND_ASSIGN(ExtensionDownloadsEventRouterData); |
| }; |
| +ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo( |
| + const std::string& e_id, |
| + const base::Time& installed) |
| + : extension_id(e_id), |
| + install_time(installed), |
| + reported(false) { |
| +} |
| + |
| +ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo() |
| + : reported(false) { |
| +} |
| + |
| +ExtensionDownloadsEventRouterData::DeterminerInfo::~DeterminerInfo() {} |
| + |
| const char ExtensionDownloadsEventRouterData::kKey[] = |
| "DownloadItem ExtensionDownloadsEventRouterData"; |
| @@ -589,8 +713,8 @@ class ManagerDestructionObserver : public DownloadManager::Observer { |
| if (!(*manager_file_existence_last_checked_)[manager]) |
| (*manager_file_existence_last_checked_)[manager] = |
| new ManagerDestructionObserver(manager); |
| - (*manager_file_existence_last_checked_)[manager] |
| - ->CheckForHistoryFilesRemovalInternal(); |
| + (*manager_file_existence_last_checked_)[manager]-> |
| + CheckForHistoryFilesRemovalInternal(); |
| } |
| private: |
| @@ -637,6 +761,7 @@ std::map<DownloadManager*, ManagerDestructionObserver*>* |
| } // namespace |
| DownloadsDownloadFunction::DownloadsDownloadFunction() {} |
| + |
| DownloadsDownloadFunction::~DownloadsDownloadFunction() {} |
| bool DownloadsDownloadFunction::RunImpl() { |
| @@ -673,7 +798,13 @@ bool DownloadsDownloadFunction::RunImpl() { |
| string16 filename16; |
| EXTENSION_FUNCTION_VALIDATE(options_value->GetString( |
| kFilenameKey, &filename16)); |
| - if (!ValidateFilename(filename16)) { |
| +#if defined(OS_WIN) |
| + base::FilePath file_path(filename16); |
| +#elif defined(OS_POSIX) |
| + base::FilePath file_path(*options.filename.get()); |
| +#endif |
| + if (!ValidateFilename(file_path) || |
| + (file_path.DirName().value() != base::FilePath::kCurrentDirectory)) { |
| error_ = download_extension_errors::kGenericError; |
| return false; |
| } |
| @@ -733,6 +864,7 @@ void DownloadsDownloadFunction::OnStarted( |
| } |
| DownloadsSearchFunction::DownloadsSearchFunction() {} |
| + |
| DownloadsSearchFunction::~DownloadsSearchFunction() {} |
| bool DownloadsSearchFunction::RunImpl() { |
| @@ -770,6 +902,7 @@ bool DownloadsSearchFunction::RunImpl() { |
| } |
| DownloadsPauseFunction::DownloadsPauseFunction() {} |
| + |
| DownloadsPauseFunction::~DownloadsPauseFunction() {} |
| bool DownloadsPauseFunction::RunImpl() { |
| @@ -793,6 +926,7 @@ bool DownloadsPauseFunction::RunImpl() { |
| } |
| DownloadsResumeFunction::DownloadsResumeFunction() {} |
| + |
| DownloadsResumeFunction::~DownloadsResumeFunction() {} |
| bool DownloadsResumeFunction::RunImpl() { |
| @@ -807,7 +941,7 @@ bool DownloadsResumeFunction::RunImpl() { |
| error_ = download_extension_errors::kInvalidOperationError; |
| } else { |
| // Note that if the item isn't paused, this will be a no-op, and |
| - // we will silently treat the extension call as a success. |
| + // the extension call will seem successful. |
| download_item->Resume(); |
| } |
| if (error_.empty()) |
| @@ -816,6 +950,7 @@ bool DownloadsResumeFunction::RunImpl() { |
| } |
| DownloadsCancelFunction::DownloadsCancelFunction() {} |
| + |
| DownloadsCancelFunction::~DownloadsCancelFunction() {} |
| bool DownloadsCancelFunction::RunImpl() { |
| @@ -827,13 +962,13 @@ bool DownloadsCancelFunction::RunImpl() { |
| if (download_item != NULL) |
| download_item->Cancel(true); |
| // |download_item| can be NULL if the download ID was invalid or if the |
| - // download is not currently active. Either way, we don't consider it a |
| - // failure. |
| + // download is not currently active. Either way, it's not a failure. |
| RecordApiFunctions(DOWNLOADS_FUNCTION_CANCEL); |
| return true; |
| } |
| DownloadsEraseFunction::DownloadsEraseFunction() {} |
| + |
| DownloadsEraseFunction::~DownloadsEraseFunction() {} |
| bool DownloadsEraseFunction::RunImpl() { |
| @@ -862,20 +997,8 @@ bool DownloadsEraseFunction::RunImpl() { |
| return true; |
| } |
| -DownloadsSetDestinationFunction::DownloadsSetDestinationFunction() {} |
| -DownloadsSetDestinationFunction::~DownloadsSetDestinationFunction() {} |
| - |
| -bool DownloadsSetDestinationFunction::RunImpl() { |
| - scoped_ptr<extensions::api::downloads::SetDestination::Params> params( |
| - extensions::api::downloads::SetDestination::Params::Create(*args_)); |
| - EXTENSION_FUNCTION_VALIDATE(params.get()); |
| - error_ = download_extension_errors::kNotImplementedError; |
| - if (error_.empty()) |
| - RecordApiFunctions(DOWNLOADS_FUNCTION_SET_DESTINATION); |
| - return error_.empty(); |
| -} |
| - |
| DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() {} |
| + |
| DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {} |
| bool DownloadsAcceptDangerFunction::RunImpl() { |
| @@ -919,6 +1042,7 @@ void DownloadsAcceptDangerFunction::DangerPromptCallback( |
| } |
| DownloadsShowFunction::DownloadsShowFunction() {} |
| + |
| DownloadsShowFunction::~DownloadsShowFunction() {} |
| bool DownloadsShowFunction::RunImpl() { |
| @@ -937,6 +1061,7 @@ bool DownloadsShowFunction::RunImpl() { |
| } |
| DownloadsOpenFunction::DownloadsOpenFunction() {} |
| + |
| DownloadsOpenFunction::~DownloadsOpenFunction() {} |
| bool DownloadsOpenFunction::RunImpl() { |
| @@ -955,6 +1080,7 @@ bool DownloadsOpenFunction::RunImpl() { |
| } |
| DownloadsDragFunction::DownloadsDragFunction() {} |
| + |
| DownloadsDragFunction::~DownloadsDragFunction() {} |
| bool DownloadsDragFunction::RunImpl() { |
| @@ -1009,8 +1135,8 @@ bool DownloadsGetFileIconFunction::RunImpl() { |
| return false; |
| } |
| // In-progress downloads return the intermediate filename for GetFullPath() |
| - // which doesn't have the final extension. Therefore we won't be able to |
| - // derive a good file icon for it. So we use GetTargetFilePath() instead. |
| + // which doesn't have the final extension. Therefore a good file icon can't be |
| + // found, so use GetTargetFilePath() instead. |
| DCHECK(icon_extractor_.get()); |
| DCHECK(icon_size == 16 || icon_size == 32); |
| EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath( |
| @@ -1038,12 +1164,138 @@ ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter( |
| ALLOW_THIS_IN_INITIALIZER_LIST(notifier_(manager, this)) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| + extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| + event_router(); |
| + if (router) |
| + router->RegisterObserver( |
| + this, extensions::event_names::kOnDownloadDeterminingFilename); |
| } |
| ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| + event_router(); |
| + if (router) |
| + router->UnregisterObserver(this); |
| +} |
| + |
| +// The method by which extensions hook into the filename determination process |
| +// is based on the method by which the omnibox API allows extensions to hook |
| +// into the omnibox autocompletion process. Extensions that wish to play a part |
| +// in the filename determination process call |
| +// chrome.downloads.onDeterminingFilename.addListener, which adds an |
| +// EventListener object to ExtensionEventRouter::listeners(). |
| +// |
| +// When a download's filename is being determined, |
| +// ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone (CVRBD) passes |
| +// 2 callbacks to ExtensionDownloadsEventRouter::OnDeterminingFilename (ODF), |
| +// which stores the callbacks in the item's ExtensionDownloadsEventRouterData |
| +// (EDERD) along with all of the extension IDs that are listening for |
| +// onDeterminingFilename events. ODF dispatches |
| +// chrome.downloads.onDeterminingFilename. |
| +// |
| +// When the extension's event handler calls suggest, |
| +// downloads_custom_bindings.js calls |
| +// DownloadsInternalDetermineFilenameFunction::RunImpl, which calls |
| +// EDER::DetermineFilename, which notifies the item's EDERD. |
| +// |
| +// When the last extension's event handler returns, EDERD calls one of the two |
| +// callbacks that CVRBD passed to ODF, allowing CDMD to complete the filename |
| +// determination process. If multiple extensions wish to override the filename, |
| +// then the extension that was last installed wins. |
| + |
| +void ExtensionDownloadsEventRouter::OnDeterminingFilename( |
| + DownloadItem* item, |
| + const base::FilePath& suggested_path, |
| + const base::Closure& no_change, |
| + const FilenameChangedCallback& change) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + extensions::ExtensionSystem* system = extensions::ExtensionSystem::Get( |
| + profile_); |
| + ExtensionDownloadsEventRouterData* data = |
| + ExtensionDownloadsEventRouterData::Get(item); |
| + data->ClearPendingDeterminers(); |
| + data->set_filename_change_callbacks(no_change, change); |
| + |
| + bool any_determiners = false; |
| + const extensions::EventListenerMap::ListenerList& listeners = |
| + system->event_router()->listeners().GetEventListenersByName( |
| + extensions::event_names::kOnDownloadDeterminingFilename); |
| + for (extensions::EventListenerMap::ListenerList::const_iterator list_iter = |
|
Matt Perry
2013/02/27 22:33:47
It might be easier for you to use Event::will_disp
benjhayden
2013/03/01 20:42:21
Done.
|
| + listeners.begin(); list_iter != listeners.end(); ++list_iter) { |
| + const extensions::EventListener& listener = **list_iter; |
| + const extensions::Extension* extension = system->extension_service()-> |
| + GetExtensionById(listener.extension_id, false/*include_disabled*/); |
| + if ((profile_->IsOffTheRecord() == |
| + listener.process->GetBrowserContext()->IsOffTheRecord()) || |
| + (extension && |
| + !extension->incognito_split_mode())) { |
|
Matt Perry
2013/02/27 22:33:47
I think you want extension_service->CanCrossIncogn
benjhayden
2013/03/01 20:42:21
moot
|
| + base::Time installed = system->extension_service()->extension_prefs()-> |
| + GetInstallTime(listener.extension_id); |
| + data->AddPendingDeterminer(listener.extension_id, installed); |
| + any_determiners = true; |
| + } |
| + } |
| + if (!any_determiners) { |
| + no_change.Run(); |
| + return; |
| + } |
| + base::DictionaryValue* json = DownloadItemToJSON( |
| + item, profile_->IsOffTheRecord()).release(); |
| + json->SetString(kFilenameKey, suggested_path.LossyDisplayName()); |
| + DispatchEvent(extensions::event_names::kOnDownloadDeterminingFilename, |
| + false, |
| + json); |
| } |
| +bool ExtensionDownloadsEventRouter::DetermineFilename( |
| + Profile* profile, |
| + bool include_incognito, |
| + const std::string& ext_id, |
| + int download_id, |
| + const base::FilePath& filename, |
| + bool overwrite) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + DownloadItem* item = GetDownloadIfInProgress( |
| + profile, include_incognito, download_id); |
| + if (!item) |
| + return false; |
| + ExtensionDownloadsEventRouterData* data = |
| + ExtensionDownloadsEventRouterData::Get(item); |
| + if (!data) |
| + return false; |
| + return data->DeterminerCallback(ext_id, filename, overwrite); |
| +} |
| + |
| +void ExtensionDownloadsEventRouter::OnListenerRemoved( |
| + const extensions::EventListenerInfo& details) { |
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + if (details.event_name != |
| + extensions::event_names::kOnDownloadDeterminingFilename) |
| + return; |
| + DownloadManager* manager = notifier_.GetManager(); |
| + if (!manager) |
| + return; |
| + DownloadManager::DownloadVector items; |
| + manager->GetAllDownloads(&items); |
| + // Notify any items that may be waiting for callbacks from this |
| + // extension/determiner. |
| + for (DownloadManager::DownloadVector::const_iterator iter = |
| + items.begin(); |
| + iter != items.end(); ++iter) { |
| + ExtensionDownloadsEventRouterData* data = |
| + ExtensionDownloadsEventRouterData::Get(*iter); |
| + // This will almost always be a no-op, however, it is possible for an |
| + // extension renderer to be unloaded while a download item is waiting |
| + // for a determiner. In that case, the download item should proceed. |
| + if (data) |
| + data->DeterminerRemoved(details.extension_id); |
| + } |
| +} |
| + |
| +// That's all the methods that have to do with filename determination. The rest |
| +// have to do with the other, less special events. |
| + |
| void ExtensionDownloadsEventRouter::OnDownloadCreated( |
| DownloadManager* manager, DownloadItem* download_item) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| @@ -1053,6 +1305,7 @@ void ExtensionDownloadsEventRouter::OnDownloadCreated( |
| scoped_ptr<base::DictionaryValue> json_item( |
| DownloadItemToJSON(download_item, profile_->IsOffTheRecord())); |
| DispatchEvent(extensions::event_names::kOnDownloadCreated, |
| + true, |
| json_item->DeepCopy()); |
| if (!ExtensionDownloadsEventRouterData::Get(download_item)) |
| new ExtensionDownloadsEventRouterData(download_item, json_item.Pass()); |
| @@ -1072,7 +1325,7 @@ void ExtensionDownloadsEventRouter::OnDownloadUpdated( |
| return; |
| } |
| scoped_ptr<base::DictionaryValue> new_json(DownloadItemToJSON( |
| - download_item, profile_->IsOffTheRecord())); |
| + download_item, profile_->IsOffTheRecord())); |
| scoped_ptr<base::DictionaryValue> delta(new base::DictionaryValue()); |
| delta->SetInteger(kIdKey, download_item->GetId()); |
| std::set<std::string> new_fields; |
| @@ -1112,7 +1365,9 @@ void ExtensionDownloadsEventRouter::OnDownloadUpdated( |
| // changed. Replace the stored json with the new json. |
| data->OnItemUpdated(); |
| if (changed) { |
| - DispatchEvent(extensions::event_names::kOnDownloadChanged, delta.release()); |
| + DispatchEvent(extensions::event_names::kOnDownloadChanged, |
| + true, |
| + delta.release()); |
| data->OnChangedFired(); |
| } |
| data->set_json(new_json.Pass()); |
| @@ -1124,33 +1379,37 @@ void ExtensionDownloadsEventRouter::OnDownloadRemoved( |
| if (download_item->IsTemporary()) |
| return; |
| DispatchEvent(extensions::event_names::kOnDownloadErased, |
| + true, |
| base::Value::CreateIntegerValue(download_item->GetId())); |
| } |
| void ExtensionDownloadsEventRouter::DispatchEvent( |
| - const char* event_name, base::Value* arg) { |
| + const char* event_name, bool include_incognito, base::Value* arg) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| + if (!extensions::ExtensionSystem::Get(profile_)->event_router()) |
| + return; |
| scoped_ptr<base::ListValue> args(new base::ListValue()); |
| args->Append(arg); |
| std::string json_args; |
| base::JSONWriter::Write(args.get(), &json_args); |
| - // There is a one EDER for each on-record Profile, and a separate EDER for |
| - // each off-record Profile, so there is exactly one EDER for each |
| - // DownloadManager. EDER only watches its own DM, so all the items that an |
| - // EDER sees are either all on-record or all off-record. However, we want |
| - // extensions in off-record contexts to see on-record items. So, if this EDER |
| - // is watching an on-record DM, and there is a corresponding off-record |
| - // Profile, then dispatch this event to both the on-record Profile and the |
| - // off-record Profile. There may or may not be an off-record Profile, so send |
| - // a *copy* of |args| to the off-record Profile, and Pass() |args| |
| - // to the Profile that we know is there. |
| - if (profile_->HasOffTheRecordProfile() && |
| - !profile_->IsOffTheRecord()) { |
| - DispatchEventInternal( |
| - profile_->GetOffTheRecordProfile(), |
| - event_name, |
| - json_args, |
| - scoped_ptr<base::ListValue>(args->DeepCopy())); |
| - } |
| - DispatchEventInternal(profile_, event_name, json_args, args.Pass()); |
| + scoped_ptr<extensions::Event> event(new extensions::Event( |
| + event_name, args.Pass())); |
| + // The downloads system wants to share on-record events with off-record |
| + // extension renderers even in incognito_split_mode because that's how |
| + // chrome://downloads works. The "restrict_to_profile" mechanism does not |
| + // anticipate this, so it does not automatically prevent sharing off-record |
| + // events with on-record extension renderers. |
| + event->restrict_to_profile = |
| + (include_incognito && !profile_->IsOffTheRecord()) ? NULL : profile_; |
| + extensions::ExtensionSystem::Get(profile_)->event_router()-> |
| + BroadcastEvent(event.Pass()); |
| + DownloadsNotificationSource notification_source; |
| + notification_source.event_name = event_name; |
| + notification_source.profile = profile_; |
| + content::Source<DownloadsNotificationSource> content_source( |
| + ¬ification_source); |
| + content::NotificationService::current()->Notify( |
| + chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, |
| + content_source, |
| + content::Details<std::string>(&json_args)); |
| } |