| 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 92d09b2b8abe8916cd9a9f3b35f9f82db70ad575..06a00d5a9b30661dc1a2820ba6440e72592ba52e 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"
|
| @@ -179,11 +180,13 @@ DownloadItem::DownloadState StateEnumFromString(const std::string& state) {
|
| }
|
|
|
| bool ValidateFilename(const string16& filename) {
|
| - // TODO(benjhayden): More robust validation of filename.
|
| - if ((filename.find('/') != string16::npos) ||
|
| + if (filename.empty() ||
|
| + (filename.find('/') != string16::npos) ||
|
| (filename.find('\\') != string16::npos))
|
| return false;
|
| - if (filename.size() >= 2u && filename[0] == L'.' && filename[1] == L'.')
|
| + if ((filename.size() >= 2u) &&
|
| + (filename[0] == L'.') &&
|
| + (filename[1] == L'.'))
|
| return false;
|
| return true;
|
| }
|
| @@ -256,8 +259,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,
|
| @@ -401,7 +403,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;
|
| @@ -536,7 +538,9 @@ 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()),
|
| + determiner_index_(-1),
|
| + determined_overwrite_(false) {
|
| download_item->SetUserData(kKey, this);
|
| }
|
|
|
| @@ -555,22 +559,95 @@ 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 AddPendingDeterminer(const std::string& extension_id,
|
| + const std::string& sub_event_id) {
|
| + determiners_.push_back(DeterminerInfo(extension_id, sub_event_id));
|
| + }
|
| +
|
| + bool DeterminerCallback(
|
| + const std::string& extension_id,
|
| + const std::string& sub_event_id,
|
| + const FilePath& filename,
|
| + bool overwrite) {
|
| + 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)) {
|
| + DCHECK(!determiners_[index].reported);
|
| + determiners_[index].reported = true;
|
| + found_info = true;
|
| + if (!filename.empty() &&
|
| + !filename.IsAbsolute() &&
|
| + (index > determiner_index_)) {
|
| + determined_filename_ = filename;
|
| + determined_overwrite_ = overwrite;
|
| + determiner_index_ = index;
|
| + }
|
| + }
|
| + }
|
| + if (!found_info)
|
| + return false;
|
| + for (size_t index = 0; index < determiners_.size(); ++index) {
|
| + if (!determiners_[index].reported)
|
| + return true;
|
| + }
|
| + determiners_.clear();
|
| + if (determined_filename_.empty()) {
|
| + filename_no_change_.Run();
|
| + } else {
|
| + filename_change_.Run(determined_filename_, determined_overwrite_);
|
| + }
|
| + return true;
|
| + }
|
| +
|
| private:
|
| + struct DeterminerInfo {
|
| + DeterminerInfo(const std::string& e_id, const std::string& sub_id);
|
| + ~DeterminerInfo();
|
| +
|
| + std::string extension_id;
|
| + std::string sub_event_id;
|
| + bool reported;
|
| + };
|
| + typedef std::vector<DeterminerInfo> DeterminerInfoVector;
|
| +
|
| static const char kKey[];
|
|
|
| int updated_;
|
| int changed_fired_;
|
| scoped_ptr<base::DictionaryValue> json_;
|
| + base::Closure filename_no_change_;
|
| + ExtensionDownloadsEventRouter::FilenameChangedCallback filename_change_;
|
| + DeterminerInfoVector determiners_;
|
| + int determiner_index_;
|
| + FilePath determined_filename_;
|
| + bool determined_overwrite_;
|
|
|
| DISALLOW_COPY_AND_ASSIGN(ExtensionDownloadsEventRouterData);
|
| };
|
|
|
| +ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo(
|
| + const std::string& e_id, const std::string& sub_id)
|
| + : extension_id(e_id), sub_event_id(sub_id) {
|
| +}
|
| +
|
| +ExtensionDownloadsEventRouterData::DeterminerInfo::~DeterminerInfo() {}
|
| +
|
| const char ExtensionDownloadsEventRouterData::kKey[] =
|
| "DownloadItem ExtensionDownloadsEventRouterData";
|
|
|
| } // namespace
|
|
|
| DownloadsDownloadFunction::DownloadsDownloadFunction() {}
|
| +
|
| DownloadsDownloadFunction::~DownloadsDownloadFunction() {}
|
|
|
| bool DownloadsDownloadFunction::RunImpl() {
|
| @@ -667,6 +744,7 @@ void DownloadsDownloadFunction::OnStarted(
|
| }
|
|
|
| DownloadsSearchFunction::DownloadsSearchFunction() {}
|
| +
|
| DownloadsSearchFunction::~DownloadsSearchFunction() {}
|
|
|
| bool DownloadsSearchFunction::RunImpl() {
|
| @@ -702,6 +780,7 @@ bool DownloadsSearchFunction::RunImpl() {
|
| }
|
|
|
| DownloadsPauseFunction::DownloadsPauseFunction() {}
|
| +
|
| DownloadsPauseFunction::~DownloadsPauseFunction() {}
|
|
|
| bool DownloadsPauseFunction::RunImpl() {
|
| @@ -725,6 +804,7 @@ bool DownloadsPauseFunction::RunImpl() {
|
| }
|
|
|
| DownloadsResumeFunction::DownloadsResumeFunction() {}
|
| +
|
| DownloadsResumeFunction::~DownloadsResumeFunction() {}
|
|
|
| bool DownloadsResumeFunction::RunImpl() {
|
| @@ -739,7 +819,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())
|
| @@ -748,6 +828,7 @@ bool DownloadsResumeFunction::RunImpl() {
|
| }
|
|
|
| DownloadsCancelFunction::DownloadsCancelFunction() {}
|
| +
|
| DownloadsCancelFunction::~DownloadsCancelFunction() {}
|
|
|
| bool DownloadsCancelFunction::RunImpl() {
|
| @@ -759,13 +840,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() {
|
| @@ -794,20 +875,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() {
|
| @@ -821,6 +890,7 @@ bool DownloadsAcceptDangerFunction::RunImpl() {
|
| }
|
|
|
| DownloadsShowFunction::DownloadsShowFunction() {}
|
| +
|
| DownloadsShowFunction::~DownloadsShowFunction() {}
|
|
|
| bool DownloadsShowFunction::RunImpl() {
|
| @@ -839,6 +909,7 @@ bool DownloadsShowFunction::RunImpl() {
|
| }
|
|
|
| DownloadsOpenFunction::DownloadsOpenFunction() {}
|
| +
|
| DownloadsOpenFunction::~DownloadsOpenFunction() {}
|
|
|
| bool DownloadsOpenFunction::RunImpl() {
|
| @@ -857,6 +928,7 @@ bool DownloadsOpenFunction::RunImpl() {
|
| }
|
|
|
| DownloadsDragFunction::DownloadsDragFunction() {}
|
| +
|
| DownloadsDragFunction::~DownloadsDragFunction() {}
|
|
|
| bool DownloadsDragFunction::RunImpl() {
|
| @@ -911,8 +983,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(
|
| @@ -933,6 +1005,94 @@ void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
|
| SendResponse(error_.empty());
|
| }
|
|
|
| +// 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::OnDownloadFilenameDetermined (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 last one wins. However, there's no deterministic way
|
| +// to determine which extension will finish last, so users are advised (in the
|
| +// developer documentation in downloads.idl) to try to use extensions that won't
|
| +// conflict.
|
| +
|
| +bool ExtensionDownloadsEventRouter::AddFilenameDeterminer(
|
| + Profile* profile,
|
| + bool include_incognito,
|
| + const std::string& ext_id,
|
| + const std::string& sub_event_id) {
|
| + if (include_incognito && profile->HasOffTheRecordProfile()) {
|
| + AddFilenameDeterminer(profile->GetOffTheRecordProfile(),
|
| + false,
|
| + ext_id,
|
| + sub_event_id);
|
| + }
|
| + ExtensionDownloadsEventRouter* router =
|
| + DownloadServiceFactory::GetForProfile(profile)->GetExtensionEventRouter();
|
| + for (DeterminerVector::const_iterator iter = router->determiners_.begin();
|
| + iter != router->determiners_.end(); ++iter) {
|
| + if (iter->first == ext_id && iter->second == sub_event_id)
|
| + return false;
|
| + }
|
| + router->determiners_.push_back(make_pair(ext_id, sub_event_id));
|
| + return true;
|
| +}
|
| +
|
| +bool ExtensionDownloadsEventRouter::RemoveFilenameDeterminer(
|
| + Profile* profile,
|
| + bool include_incognito,
|
| + const std::string& ext_id,
|
| + const std::string& sub_event_id) {
|
| + if (include_incognito && profile->HasOffTheRecordProfile()) {
|
| + RemoveFilenameDeterminer(profile->GetOffTheRecordProfile(),
|
| + false,
|
| + ext_id,
|
| + sub_event_id);
|
| + }
|
| + ExtensionDownloadsEventRouter* router =
|
| + DownloadServiceFactory::GetForProfile(profile)->GetExtensionEventRouter();
|
| + for (DeterminerVector::iterator iter = router->determiners_.begin();
|
| + iter != router->determiners_.end(); ++iter) {
|
| + if (iter->first == ext_id && iter->second == sub_event_id) {
|
| + router->determiners_.erase(iter);
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool ExtensionDownloadsEventRouter::DetermineFilename(
|
| + Profile* profile,
|
| + bool include_incognito,
|
| + const std::string& ext_id,
|
| + const std::string& sub_event_id,
|
| + int download_id,
|
| + const FilePath& filename,
|
| + bool overwrite) {
|
| + 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);
|
| +}
|
| +
|
| ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(
|
| Profile* profile,
|
| DownloadManager* manager)
|
| @@ -940,6 +1100,29 @@ 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_));
|
| +}
|
| +
|
| +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;
|
| + for (DeterminerVector::iterator iter = determiners_.begin();
|
| + iter != determiners_.end(); ++iter) {
|
| + if (iter->first == extension->id())
|
| + iter = determiners_.erase(iter);
|
| + }
|
| + break;
|
| + }
|
| + default:
|
| + NOTREACHED();
|
| + break;
|
| + }
|
| }
|
|
|
| ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() {
|
| @@ -960,6 +1143,38 @@ void ExtensionDownloadsEventRouter::OnDownloadCreated(
|
| new ExtensionDownloadsEventRouterData(download_item, json_item.Pass());
|
| }
|
|
|
| +void ExtensionDownloadsEventRouter::OnDownloadFilenameDetermined(
|
| + DownloadItem* item,
|
| + const FilePath& suggested_path,
|
| + const base::Closure& no_change,
|
| + const FilenameChangedCallback& change) {
|
| + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| + if (determiners_.empty()) {
|
| + no_change.Run();
|
| + return;
|
| + }
|
| +
|
| + ExtensionDownloadsEventRouterData* data =
|
| + ExtensionDownloadsEventRouterData::Get(item);
|
| + data->set_filename_change_callbacks(no_change, change);
|
| + std::set<std::string> sub_events;
|
| + for (DeterminerVector::const_iterator iter = determiners_.begin();
|
| + iter != determiners_.end(); ++iter) {
|
| + data->AddPendingDeterminer(iter->first, iter->second);
|
| + sub_events.insert(iter->second);
|
| + }
|
| + std::string event_name(
|
| + extensions::event_names::kOnDownloadDeterminingFilename);
|
| + event_name += "/";
|
| + for (std::set<std::string>::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());
|
| + DispatchEvent((event_name + (*iter)).c_str(), json);
|
| + }
|
| +}
|
| +
|
| void ExtensionDownloadsEventRouter::OnDownloadUpdated(
|
| DownloadManager* manager, DownloadItem* download_item) {
|
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
|
| @@ -974,7 +1189,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;
|
| @@ -1039,13 +1254,13 @@ void ExtensionDownloadsEventRouter::DispatchEvent(
|
| // 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.
|
| + // EDER sees are either all on-record or all off-record. However, extensions
|
| + // in off-record contexts should 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
|
| + // is certainly there.
|
| if (profile_->HasOffTheRecordProfile() &&
|
| !profile_->IsOffTheRecord()) {
|
| DispatchEventInternal(
|
|
|