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 df50565ed39712137fdac97d7c83f43b2a48b1f7..1ce0b24830abaf0d3e0fb967fc840e76e83ad978 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" |
@@ -184,14 +189,11 @@ 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() && |
asanka
2013/02/22 18:55:48
"foo/" shouldn't be considered a valid filename ei
benjhayden
2013/02/22 20:43:23
I believe that the next line handles that case.
asanka
2013/02/22 21:22:29
For "foo/" BaseName() returns "foo".
|
+ (filename.BaseName().value() != base::FilePath::kCurrentDirectory) && |
+ !filename.ReferencesParent() && |
+ !filename.IsAbsolute(); |
} |
std::string TimeToISO8601(const base::Time& t) { |
@@ -263,8 +265,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, |
@@ -410,7 +411,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; |
@@ -507,31 +508,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) { |
@@ -545,7 +521,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); |
} |
@@ -564,16 +541,166 @@ 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, |
+ int sub_event_id) { |
+ for (DeterminerInfoVector::iterator iter = determiners_.begin(); |
+ iter != determiners_.end();) { |
+ if ((iter->extension_id == extension_id) && |
+ (iter->sub_event_id == sub_event_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, |
+ int sub_event_id, |
+ const base::Time& installed) { |
+ determiners_.push_back(DeterminerInfo( |
+ extension_id, sub_event_id, installed)); |
+ } |
+ |
+ bool DeterminerCallback( |
+ const std::string& extension_id, |
+ int sub_event_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()); |
+ ++index) { |
+ if ((determiners_[index].extension_id == extension_id) && |
+ (determiners_[index].sub_event_id == sub_event_id)) { |
+ found_info = true; |
asanka
2013/02/22 18:55:48
Curious: Is it possible for there to be more than
benjhayden
2013/02/22 20:43:23
I hope not. :-)
See AddPendingDeterminer and searc
|
+ DCHECK(!determiners_[index].reported); |
+ determiners_[index].reported = true; |
+ if (!determiner_.extension_id.empty() && |
+ (determiners_[index].extension_id == determiner_.extension_id)) { |
+ DCHECK_NE(determiners_[index].sub_event_id, determiner_.sub_event_id); |
+ } |
+ // 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, and listeners that |
+ // were added later take precedence over previous listeners. |
+ if (filename_valid && |
+ (determiner_.extension_id.empty() || |
+ ((determiners_[index].extension_id == determiner_.extension_id) ? |
+ (determiners_[index].sub_event_id > determiner_.sub_event_id) : |
+ (determiners_[index].install_time > determiner_.install_time)))) { |
+ determined_filename_ = filename; |
+ determined_overwrite_ = overwrite; |
+ determiner_ = determiners_[index]; |
+ } |
+ } |
+ } |
+ if (!found_info) |
+ return false; |
+ CheckAllDeterminersCalled(); |
+ return filename.empty() || filename_valid; |
+ } |
+ |
private: |
+ struct DeterminerInfo { |
+ DeterminerInfo(); |
+ DeterminerInfo(const std::string& e_id, |
+ int sub_id, |
+ const base::Time& installed); |
+ ~DeterminerInfo(); |
+ |
+ std::string extension_id; |
+ int sub_event_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, |
+ int sub_id, |
+ const base::Time& installed) |
+ : extension_id(e_id), |
+ sub_event_id(sub_id), |
+ install_time(installed), |
+ reported(false) { |
+} |
+ |
+ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo() |
+ : reported(false) { |
+} |
+ |
+ExtensionDownloadsEventRouterData::DeterminerInfo::~DeterminerInfo() {} |
+ |
const char ExtensionDownloadsEventRouterData::kKey[] = |
"DownloadItem ExtensionDownloadsEventRouterData"; |
@@ -636,6 +763,7 @@ std::map<DownloadManager*, ManagerDestructionObserver*>* |
} // namespace |
DownloadsDownloadFunction::DownloadsDownloadFunction() {} |
+ |
DownloadsDownloadFunction::~DownloadsDownloadFunction() {} |
bool DownloadsDownloadFunction::RunImpl() { |
@@ -672,7 +800,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; |
} |
@@ -732,6 +866,7 @@ void DownloadsDownloadFunction::OnStarted( |
} |
DownloadsSearchFunction::DownloadsSearchFunction() {} |
+ |
DownloadsSearchFunction::~DownloadsSearchFunction() {} |
bool DownloadsSearchFunction::RunImpl() { |
@@ -769,6 +904,7 @@ bool DownloadsSearchFunction::RunImpl() { |
} |
DownloadsPauseFunction::DownloadsPauseFunction() {} |
+ |
DownloadsPauseFunction::~DownloadsPauseFunction() {} |
bool DownloadsPauseFunction::RunImpl() { |
@@ -792,6 +928,7 @@ bool DownloadsPauseFunction::RunImpl() { |
} |
DownloadsResumeFunction::DownloadsResumeFunction() {} |
+ |
DownloadsResumeFunction::~DownloadsResumeFunction() {} |
bool DownloadsResumeFunction::RunImpl() { |
@@ -806,7 +943,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()) |
@@ -815,6 +952,7 @@ bool DownloadsResumeFunction::RunImpl() { |
} |
DownloadsCancelFunction::DownloadsCancelFunction() {} |
+ |
DownloadsCancelFunction::~DownloadsCancelFunction() {} |
bool DownloadsCancelFunction::RunImpl() { |
@@ -826,13 +964,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() { |
@@ -861,20 +999,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() { |
@@ -918,6 +1044,7 @@ void DownloadsAcceptDangerFunction::DangerPromptCallback( |
} |
DownloadsShowFunction::DownloadsShowFunction() {} |
+ |
DownloadsShowFunction::~DownloadsShowFunction() {} |
bool DownloadsShowFunction::RunImpl() { |
@@ -936,6 +1063,7 @@ bool DownloadsShowFunction::RunImpl() { |
} |
DownloadsOpenFunction::DownloadsOpenFunction() {} |
+ |
DownloadsOpenFunction::~DownloadsOpenFunction() {} |
bool DownloadsOpenFunction::RunImpl() { |
@@ -954,6 +1082,7 @@ bool DownloadsOpenFunction::RunImpl() { |
} |
DownloadsDragFunction::DownloadsDragFunction() {} |
+ |
DownloadsDragFunction::~DownloadsDragFunction() {} |
bool DownloadsDragFunction::RunImpl() { |
@@ -1008,8 +1137,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( |
@@ -1030,6 +1159,19 @@ void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) { |
SendResponse(error_.empty()); |
} |
+ExtensionDownloadsEventRouter::DeterminerId::DeterminerId( |
+ const std::string& ext_id, |
+ int sub_id, |
+ bool incognito, |
+ const base::Time& install) |
+ : extension_id(ext_id), |
+ sub_event_id(sub_id), |
+ include_incognito(incognito), |
+ installed(install) { |
+} |
+ |
+ExtensionDownloadsEventRouter::DeterminerId::~DeterminerId() {} |
+ |
ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter( |
Profile* profile, |
DownloadManager* manager) |
@@ -1037,12 +1179,230 @@ ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter( |
ALLOW_THIS_IN_INITIALIZER_LIST(notifier_(manager, this)) { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
DCHECK(profile_); |
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
+ content::Source<Profile>(profile_)); |
} |
ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() { |
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
} |
+// The method by which extensions hook into the filename determination process |
+// is based on the method by which the webRequest API allows extensions to hook |
+// into the resource loading process. Extensions that wish to play a part in |
+// the filename determination process call |
+// chrome.downloads.onDeterminingFilename.addListener, which secretly (see |
+// chrome/renderer/resources/extensions/downloads_custom_bindings.js) calls |
+// DownloadsInternalAddFilenameDeterminerFunction (see ../downloads_internal/), |
+// which adds the extension's ID to the profile's ExtensionDownloadsEventRouter |
+// (EDER). |
+// |
+// When a download's filename is being determined, |
+// ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone (CVRBD) passes |
+// 2 callbacks to EDER::OnDeterminingFilename (ODFD), which stores the |
+// callbacks in the item's ExtensionDownloadsEventRouterData (EDERD) along with |
+// all of the extension IDs that are listening for onDeterminingFilename events. |
+// ODFD dispatches chrome.downloads.onDeterminingFilename. |
+// |
+// When the extension's event handler returns, downloads_custom_bindings.js |
+// calls DownloadsInternalDetermineFilenameFunction::RunImpl, 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 ODFD, allowing CDMD to complete the filename |
+// determination process. If multiple extensions wish to override the filename, |
+// then the extension that was last installed wins; if multiple determiners |
+// within an extension compete, then the determiner that was last added wins. |
+ |
+bool ExtensionDownloadsEventRouter::AddFilenameDeterminer( |
+ Profile* profile, |
+ bool include_incognito, |
+ const std::string& ext_id, |
+ int sub_event_id) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ // Only incognito/regular renderer processes receive onDeterminingFilename |
+ // events for incognito/regular downloads. Do not automatically add this |
+ // determiner to the other (incognito/regular) EDER; if the extension wants to |
+ // listen for onDeterminingFilename in those extension renderer processes, |
+ // then it will call addListener there, and |profile| will be |
+ // incognito/regular for those addListener calls, so they will use the |
+ // incognito/regular EDER. |
+ ExtensionDownloadsEventRouter* router = |
+ DownloadServiceFactory::GetForProfile(profile)->GetExtensionEventRouter(); |
+ if (!router) |
+ return false; |
+ for (DeterminerVector::const_iterator iter = router->determiners_.begin(); |
+ iter != router->determiners_.end(); ++iter) { |
+ if (iter->extension_id == ext_id) { |
+ DCHECK_NE(iter->sub_event_id, sub_event_id); |
+ } |
+ if ((iter->extension_id == ext_id) && |
+ (iter->sub_event_id == sub_event_id)) |
+ return false; |
+ } |
+ base::Time installed = extensions::ExtensionSystem::Get( |
+ profile)->extension_service()->extension_prefs()->GetInstallTime(ext_id); |
+ router->determiners_.push_back(DeterminerId( |
+ ext_id, sub_event_id, include_incognito, installed)); |
+ return true; |
+} |
+ |
+bool ExtensionDownloadsEventRouter::RemoveFilenameDeterminer( |
+ Profile* profile, |
+ const std::string& ext_id, |
+ int sub_event_id) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ ExtensionDownloadsEventRouter* router = |
+ DownloadServiceFactory::GetForProfile(profile)->GetExtensionEventRouter(); |
+ for (DeterminerVector::iterator iter = router->determiners_.begin(); |
+ iter != router->determiners_.end(); ++iter) { |
+ if (iter->extension_id == ext_id && iter->sub_event_id == sub_event_id) { |
+ router->determiners_.erase(iter); |
+ // Notify any items that may be waiting for callbacks from this |
+ // determiner. |
+ DownloadManager* manager = router->notifier_.GetManager(); |
+ if (manager) { |
+ DownloadManager::DownloadVector items; |
+ manager->GetAllDownloads(&items); |
+ for (DownloadManager::DownloadVector::const_iterator iter = |
+ items.begin(); |
+ iter != items.end(); ++iter) { |
+ ExtensionDownloadsEventRouterData* data = |
+ ExtensionDownloadsEventRouterData::Get(*iter); |
+ if (data) |
+ data->DeterminerRemoved(ext_id, sub_event_id); |
+ } |
+ } |
+ return true; |
+ } |
+ } |
+ DCHECK(false) << ext_id << " " << sub_event_id; |
+ return false; |
+} |
+ |
+void ExtensionDownloadsEventRouter::OnDeterminingFilename( |
+ DownloadItem* item, |
+ const base::FilePath& suggested_path, |
+ const base::Closure& no_change, |
+ const FilenameChangedCallback& change) { |
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ bool any_determiners = false; |
+ |
+ ExtensionDownloadsEventRouterData* data = |
+ ExtensionDownloadsEventRouterData::Get(item); |
+ data->ClearPendingDeterminers(); |
asanka
2013/02/22 18:55:48
Is it possible for there to be a pending determine
benjhayden
2013/02/22 20:43:23
Yep, Randy suggested clearing pending determiners
|
+ data->set_filename_change_callbacks(no_change, change); |
+ std::set<int> sub_events; |
+ for (DeterminerVector::const_iterator iter = determiners_.begin(); |
+ iter != determiners_.end(); ++iter) { |
+ data->AddPendingDeterminer(iter->extension_id, |
+ iter->sub_event_id, |
+ iter->installed); |
+ sub_events.insert(iter->sub_event_id); |
+ any_determiners = true; |
+ } |
+ |
+ if (profile_->IsOffTheRecord()) { |
+ // If this DownloadItem is incognito and there are any |
+ // incognito-spanning-mode extensions registered in the on-record EDER, then |
+ // dispatch to them and wait for them as well. |
+ ExtensionDownloadsEventRouter* router = |
+ DownloadServiceFactory::GetForProfile( |
+ profile_->GetOriginalProfile())->GetExtensionEventRouter(); |
+ for (DeterminerVector::const_iterator iter = router->determiners_.begin(); |
+ iter != router->determiners_.end(); ++iter) { |
+ if (iter->include_incognito) { |
+ data->AddPendingDeterminer(iter->extension_id, |
+ iter->sub_event_id, |
+ iter->installed); |
+ sub_events.insert(iter->sub_event_id); |
+ any_determiners = true; |
+ } |
+ } |
+ } |
+ |
+ std::string event_name( |
+ extensions::event_names::kOnDownloadDeterminingFilename); |
+ event_name += "/"; |
+ for (std::set<int>::const_iterator iter = sub_events.begin(); |
+ iter != sub_events.end(); ++iter) { |
+ base::DictionaryValue* json = DownloadItemToJSON( |
+ item, profile_->IsOffTheRecord()).release(); |
+ json->SetString(kFilenameKey, suggested_path.LossyDisplayName()); |
+ // If there's a listener in an incognito extension renderer, then it will |
+ // have registered its own determiner in the incognito EDER, which will be |
+ // called for incognito downloads. |
+ DispatchEvent((event_name + base::IntToString(*iter)).c_str(), false, json); |
+ } |
+ |
+ if (!any_determiners) { |
+ no_change.Run(); |
+ return; |
+ } |
+} |
+ |
+bool ExtensionDownloadsEventRouter::DetermineFilename( |
+ Profile* profile, |
+ bool include_incognito, |
+ const std::string& ext_id, |
+ int sub_event_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, sub_event_id, filename, overwrite); |
+} |
+ |
+void ExtensionDownloadsEventRouter::Observe( |
+ int type, |
+ const content::NotificationSource& source, |
+ const content::NotificationDetails& details) { |
+ switch (type) { |
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED: { |
+ const extensions::Extension* extension = content::Details< |
+ extensions::UnloadedExtensionInfo>(details)->extension; |
+ // Remove this extension from |determiners_|. |
+ for (DeterminerVector::iterator iter = determiners_.begin(); |
+ iter != determiners_.end();) { |
+ if (iter->extension_id == extension->id()) { |
+ iter = determiners_.erase(iter); |
+ } else { |
+ ++iter; |
+ } |
+ } |
+ // Notify any items that may be waiting for callbacks from this extension. |
+ DownloadManager* manager = notifier_.GetManager(); |
+ if (manager) { |
+ DownloadManager::DownloadVector items; |
+ manager->GetAllDownloads(&items); |
+ for (DownloadManager::DownloadVector::const_iterator iter = |
+ items.begin(); |
+ iter != items.end(); ++iter) { |
+ ExtensionDownloadsEventRouterData* data = |
+ ExtensionDownloadsEventRouterData::Get(*iter); |
+ if (data) |
+ data->ExtensionUnloaded(extension->id()); |
+ } |
+ } |
+ break; |
+ } |
+ default: |
+ NOTREACHED(); |
+ break; |
+ } |
+} |
+ |
+// 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)); |
@@ -1052,6 +1412,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()); |
@@ -1071,7 +1432,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; |
@@ -1111,7 +1472,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()); |
@@ -1123,33 +1486,35 @@ 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())); |
+ // "restrict_to_profile" is not smart enough to avoid sharing off-record |
+ // events with on-record extension renderers. |
Matt Perry
2013/02/22 01:43:43
This comment is a bit cryptic to me.
The main qui
benjhayden
2013/02/22 20:43:23
Done.
|
+ event->restrict_to_profile = |
+ (include_incognito && !profile_->IsOffTheRecord()) ? NULL : profile_; |
+ extensions::ExtensionSystem::Get(profile_)->event_router()-> |
+ BroadcastEvent(event.Pass()); |
+ ExtensionDownloadsEventRouter::DownloadsNotificationSource |
Matt Perry
2013/02/22 01:43:43
nit: you're already in this scope so you can drop
benjhayden
2013/02/22 20:43:23
Done.
|
+ notification_source; |
+ notification_source.event_name = event_name; |
+ notification_source.profile = profile_; |
+ content::Source<ExtensionDownloadsEventRouter::DownloadsNotificationSource> |
Matt Perry
2013/02/22 01:43:43
same here
benjhayden
2013/02/22 20:43:23
Done.
|
+ content_source(¬ification_source); |
+ content::NotificationService::current()->Notify( |
+ chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, |
+ content_source, |
+ content::Details<std::string>(&json_args)); |
} |