| 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..2ccafb542b882b0154e65f4ffdf240e5f3ab67c3 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() &&
|
| + (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;
|
| + 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";
|
|
|
| @@ -588,8 +715,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:
|
| @@ -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,233 @@ 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, std::string(
|
| + extensions::event_names::kOnDownloadDeterminingFilename) + "/1");
|
| }
|
|
|
| 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 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.
|
| +
|
| +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();
|
| + 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::OnListenerAdded(
|
| + const extensions::EventListenerInfo& details) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + // An extension has started listening for "onDeterminingFilename/N". It may
|
| + // start listening for "onDeterminingFilename/(N+1)", so add an EventRouter
|
| + // Observer to listen for when that listener is added.
|
| + if (!StartsWithASCII(
|
| + details.event_name,
|
| + (std::string(extensions::event_names::kOnDownloadDeterminingFilename) +
|
| + "/"),
|
| + true/*case_sensitive*/))
|
| + return;
|
| + std::string sub_event_id_str = details.event_name.substr(strlen(
|
| + extensions::event_names::kOnDownloadDeterminingFilename) + 1);
|
| + int sub_event_id = -1;
|
| + if (!base::StringToInt(sub_event_id_str, &sub_event_id))
|
| + return;
|
| +
|
| + // 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.
|
| + bool already_added = false;
|
| + for (DeterminerVector::const_iterator iter = determiners_.begin();
|
| + iter != determiners_.end(); ++iter) {
|
| + if (iter->extension_id == details.extension_id) {
|
| + DCHECK_NE(iter->sub_event_id, sub_event_id);
|
| + }
|
| + if ((iter->extension_id == details.extension_id) &&
|
| + (iter->sub_event_id == sub_event_id)) {
|
| + already_added = true;
|
| + break;
|
| + }
|
| + }
|
| + const extensions::Extension* extension = extensions::ExtensionSystem::Get(
|
| + profile_)->extension_service()->GetExtensionById(
|
| + details.extension_id, false/*include_disabled*/);
|
| + if (!already_added && extension) {
|
| + base::Time installed = extensions::ExtensionSystem::Get(profile_)->
|
| + extension_service()->extension_prefs()->GetInstallTime(
|
| + details.extension_id);
|
| + bool include_incognito = !extension->incognito_split_mode();
|
| + determiners_.push_back(DeterminerId(
|
| + details.extension_id, sub_event_id, include_incognito, installed));
|
| + }
|
| +
|
| + // It doesn't hurt to register as an observer for the same event_name more
|
| + // than once.
|
| + extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)->
|
| + event_router();
|
| + if (!router)
|
| + return;
|
| + std::string next_listener(base::StringPrintf(
|
| + "%s/%d",
|
| + extensions::event_names::kOnDownloadDeterminingFilename,
|
| + sub_event_id + 1));
|
| + router->RegisterObserver(this, next_listener);
|
| +}
|
| +
|
| +void ExtensionDownloadsEventRouter::OnListenerRemoved(
|
| + const extensions::EventListenerInfo& details) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + if (!StartsWithASCII(
|
| + details.event_name,
|
| + (std::string(extensions::event_names::kOnDownloadDeterminingFilename) +
|
| + "/"),
|
| + true/*case_sensitive*/))
|
| + return;
|
| + std::string sub_event_id_str = details.event_name.substr(strlen(
|
| + extensions::event_names::kOnDownloadDeterminingFilename) + 1);
|
| + int sub_event_id = -1;
|
| + if (!base::StringToInt(sub_event_id_str, &sub_event_id))
|
| + return;
|
| + DownloadManager* manager = notifier_.GetManager();
|
| + if (!manager)
|
| + return;
|
| + DownloadManager::DownloadVector items;
|
| + manager->GetAllDownloads(&items);
|
| + for (DeterminerVector::iterator iter = determiners_.begin();
|
| + iter != determiners_.end(); ++iter) {
|
| + if ((iter->extension_id == details.extension_id) &&
|
| + (iter->sub_event_id == sub_event_id)) {
|
| + determiners_.erase(iter);
|
| + // Notify any items that may be waiting for callbacks from this
|
| + // determiner.
|
| + for (DownloadManager::DownloadVector::const_iterator iter =
|
| + items.begin();
|
| + iter != items.end(); ++iter) {
|
| + ExtensionDownloadsEventRouterData* data =
|
| + ExtensionDownloadsEventRouterData::Get(*iter);
|
| + if (data)
|
| + data->DeterminerRemoved(details.extension_id, sub_event_id);
|
| + }
|
| + return;
|
| + }
|
| + }
|
| +}
|
| +
|
| +// 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 +1415,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 +1435,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 +1475,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 +1489,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.
|
| + event->restrict_to_profile =
|
| + (include_incognito && !profile_->IsOffTheRecord()) ? NULL : profile_;
|
| + extensions::ExtensionSystem::Get(profile_)->event_router()->
|
| + BroadcastEvent(event.Pass());
|
| + ExtensionDownloadsEventRouter::DownloadsNotificationSource
|
| + notification_source;
|
| + notification_source.event_name = event_name;
|
| + notification_source.profile = profile_;
|
| + content::Source<ExtensionDownloadsEventRouter::DownloadsNotificationSource>
|
| + content_source(¬ification_source);
|
| + content::NotificationService::current()->Notify(
|
| + chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
|
| + content_source,
|
| + content::Details<std::string>(&json_args));
|
| }
|
|
|