Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4225)

Unified Diff: chrome/browser/extensions/api/downloads/downloads_api.cc

Issue 11574006: Implement chrome.downloads.onDeterminingFilename() (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: @r185803 Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 117c93a5830c637d4ea70d35333f9a84328082b7..60831b9513a2d3dcc94bf9957d9bb7aaa3073306 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -14,9 +14,11 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
+#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/string16.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"
@@ -68,16 +73,20 @@ using content::DownloadManager;
namespace download_extension_errors {
// Error messages
-const char kGenericError[] = "I'm afraid I can't do that.";
-const char kIconNotFoundError[] = "Icon not found.";
+const char kGenericError[] = "I'm afraid I can't do that";
+const char kIconNotFoundError[] = "Icon not found";
const char kInvalidDangerTypeError[] = "Invalid danger type";
+const char kInvalidFilenameError[] = "Invalid filename";
const char kInvalidFilterError[] = "Invalid query filter";
-const char kInvalidOperationError[] = "Invalid operation.";
+const char kInvalidOperationError[] = "Invalid operation";
const char kInvalidOrderByError[] = "Invalid orderBy field";
const char kInvalidQueryLimit[] = "Invalid query limit";
const char kInvalidStateError[] = "Invalid state";
-const char kInvalidURLError[] = "Invalid URL.";
+const char kInvalidURLError[] = "Invalid URL";
const char kNotImplementedError[] = "NotImplemented.";
+const char kTooManyListenersError[] = "Each extension may have at most one "
+ "onDeterminingFilename listener between all of its renderer execution "
+ "contexts.";
} // namespace download_extension_errors
@@ -185,14 +194,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 +271,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 +417,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 +514,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(&notification_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 +527,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 +547,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() {
+ determined_filename_.clear();
+ determined_overwrite_ = false;
+ determiner_ = DeterminerInfo();
+ filename_no_change_ = base::Closure();
+ filename_change_ = ExtensionDownloadsEventRouter::FilenameChangedCallback();
+ weak_ptr_factory_.reset();
+ determiners_.clear();
+ }
+
+ void DeterminerRemoved(const std::string& extension_id) {
+ 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 (size_t index = 0; index < determiners_.size(); ++index) {
+ if (determiners_[index].extension_id == extension_id) {
+ DCHECK(false) << extension_id;
+ return;
+ }
+ }
+ determiners_.push_back(DeterminerInfo(extension_id, installed));
+ }
+
+ bool DeterminerAlreadyReported(const std::string& extension_id) {
+ for (size_t index = 0; index < determiners_.size(); ++index) {
+ if (determiners_[index].extension_id == extension_id) {
+ return determiners_[index].reported;
+ }
+ }
+ return false;
+ }
+
+ // Returns false if this extension_id was not expected or if this extension_id
+ // has already reported, regardless of whether the filename is valid.
+ bool DeterminerCallback(
+ const std::string& extension_id,
+ const base::FilePath& filename,
+ bool overwrite) {
+ bool found_info = false;
+ for (size_t index = 0; index < determiners_.size(); ++index) {
+ if (determiners_[index].extension_id == extension_id) {
+ found_info = true;
+ 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 (ValidateFilename(filename) &&
+ (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 true;
+ }
+
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_);
+ }
+ // Don't clear determiners_ immediately in case there's a second listener
+ // for one of the extensions, so that DetermineFilename can return
+ // kTooManyListenersError. After a few seconds, DetermineFilename will
+ // return kInvalidOperationError instead of kTooManyListenersError so that
+ // determiners_ doesn't keep hogging memory.
+ weak_ptr_factory_.reset(
+ new base::WeakPtrFactory<ExtensionDownloadsEventRouterData>(this));
+ MessageLoopForUI::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ExtensionDownloadsEventRouterData::ClearPendingDeterminers,
+ weak_ptr_factory_->GetWeakPtr()),
+ base::TimeDelta::FromSeconds(30));
+ }
+
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_;
+
+ scoped_ptr<base::WeakPtrFactory<ExtensionDownloadsEventRouterData> >
+ weak_ptr_factory_;
+
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 +721,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:
@@ -634,9 +766,23 @@ class ManagerDestructionObserver : public DownloadManager::Observer {
std::map<DownloadManager*, ManagerDestructionObserver*>*
ManagerDestructionObserver::manager_file_existence_last_checked_ = NULL;
+void OnDeterminingFilenameWillDispatchCallback(
+ bool* any_determiners,
+ ExtensionDownloadsEventRouterData* data,
+ Profile* profile,
+ const extensions::Extension* extension,
+ ListValue* event_args) {
+ *any_determiners = true;
+ base::Time installed = extensions::ExtensionSystem::Get(
+ profile)->extension_service()->extension_prefs()->
+ GetInstallTime(extension->id());
+ data->AddPendingDeterminer(extension->id(), installed);
+}
+
} // namespace
DownloadsDownloadFunction::DownloadsDownloadFunction() {}
+
DownloadsDownloadFunction::~DownloadsDownloadFunction() {}
bool DownloadsDownloadFunction::RunImpl() {
@@ -673,8 +819,14 @@ bool DownloadsDownloadFunction::RunImpl() {
string16 filename16;
EXTENSION_FUNCTION_VALIDATE(options_value->GetString(
kFilenameKey, &filename16));
- if (!ValidateFilename(filename16)) {
- error_ = download_extension_errors::kGenericError;
+#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::kInvalidFilenameError;
return false;
}
// TODO(benjhayden) Ensure that this filename is interpreted as a path
@@ -733,6 +885,7 @@ void DownloadsDownloadFunction::OnStarted(
}
DownloadsSearchFunction::DownloadsSearchFunction() {}
+
DownloadsSearchFunction::~DownloadsSearchFunction() {}
bool DownloadsSearchFunction::RunImpl() {
@@ -770,6 +923,7 @@ bool DownloadsSearchFunction::RunImpl() {
}
DownloadsPauseFunction::DownloadsPauseFunction() {}
+
DownloadsPauseFunction::~DownloadsPauseFunction() {}
bool DownloadsPauseFunction::RunImpl() {
@@ -793,6 +947,7 @@ bool DownloadsPauseFunction::RunImpl() {
}
DownloadsResumeFunction::DownloadsResumeFunction() {}
+
DownloadsResumeFunction::~DownloadsResumeFunction() {}
bool DownloadsResumeFunction::RunImpl() {
@@ -807,7 +962,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 +971,7 @@ bool DownloadsResumeFunction::RunImpl() {
}
DownloadsCancelFunction::DownloadsCancelFunction() {}
+
DownloadsCancelFunction::~DownloadsCancelFunction() {}
bool DownloadsCancelFunction::RunImpl() {
@@ -827,13 +983,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 +1018,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 +1063,7 @@ void DownloadsAcceptDangerFunction::DangerPromptCallback(
}
DownloadsShowFunction::DownloadsShowFunction() {}
+
DownloadsShowFunction::~DownloadsShowFunction() {}
bool DownloadsShowFunction::RunImpl() {
@@ -937,6 +1082,7 @@ bool DownloadsShowFunction::RunImpl() {
}
DownloadsOpenFunction::DownloadsOpenFunction() {}
+
DownloadsOpenFunction::~DownloadsOpenFunction() {}
bool DownloadsOpenFunction::RunImpl() {
@@ -955,6 +1101,7 @@ bool DownloadsOpenFunction::RunImpl() {
}
DownloadsDragFunction::DownloadsDragFunction() {}
+
DownloadsDragFunction::~DownloadsDragFunction() {}
bool DownloadsDragFunction::RunImpl() {
@@ -1009,8 +1156,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 +1185,147 @@ 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 |suggestCallback|,
+// 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));
+ ExtensionDownloadsEventRouterData* data =
+ ExtensionDownloadsEventRouterData::Get(item);
+ data->ClearPendingDeterminers();
+ data->set_filename_change_callbacks(no_change, change);
+ bool any_determiners = false;
+ base::DictionaryValue* json = DownloadItemToJSON(
+ item, profile_->IsOffTheRecord()).release();
+ json->SetString(kFilenameKey, suggested_path.LossyDisplayName());
+ DispatchEvent(extensions::event_names::kOnDownloadDeterminingFilename,
+ false,
+ base::Bind(&OnDeterminingFilenameWillDispatchCallback,
+ &any_determiners,
+ data),
+ json);
+ if (!any_determiners) {
+ data->ClearPendingDeterminers();
+ no_change.Run();
+ }
+}
+
+bool ExtensionDownloadsEventRouter::DetermineFilename(
+ Profile* profile,
+ bool include_incognito,
+ const std::string& ext_id,
+ int download_id,
+ const base::FilePath& filename,
+ bool overwrite,
+ std::string* error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DownloadItem* item = GetDownload(profile, include_incognito, download_id);
+ if (!item) {
+ *error = download_extension_errors::kInvalidOperationError;
+ return false;
+ }
+ ExtensionDownloadsEventRouterData* data =
+ ExtensionDownloadsEventRouterData::Get(item);
+ if (!data) {
+ *error = download_extension_errors::kInvalidOperationError;
+ return false;
+ }
+ // maxListeners=1 in downloads.idl and suggestCallback in
+ // downloads_custom_bindings.js should prevent duplicate DeterminerCallback
+ // calls from the same renderer, but an extension may have more than one
+ // renderer, so don't DCHECK(!reported).
+ if (data->DeterminerAlreadyReported(ext_id)) {
+ *error = download_extension_errors::kTooManyListenersError;
+ return false;
+ }
+ if (!item->IsInProgress()) {
+ *error = download_extension_errors::kInvalidOperationError;
+ return false;
+ }
+ if (!data->DeterminerCallback(ext_id, filename, overwrite)) {
+ // Nobody expects this ext_id!
+ *error = download_extension_errors::kInvalidOperationError;
+ return false;
+ }
+ if (!filename.empty() && !ValidateFilename(filename)) {
+ // If this is moved to before DeterminerCallback(), then it will block
+ // forever waiting for this ext_id to report.
+ *error = download_extension_errors::kInvalidFilenameError;
+ return false;
+ }
+ return true;
+}
+
+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 +1335,8 @@ void ExtensionDownloadsEventRouter::OnDownloadCreated(
scoped_ptr<base::DictionaryValue> json_item(
DownloadItemToJSON(download_item, profile_->IsOffTheRecord()));
DispatchEvent(extensions::event_names::kOnDownloadCreated,
+ true,
+ extensions::Event::WillDispatchCallback(),
json_item->DeepCopy());
if (!ExtensionDownloadsEventRouterData::Get(download_item))
new ExtensionDownloadsEventRouterData(download_item, json_item.Pass());
@@ -1072,7 +1356,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 +1396,10 @@ 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,
+ extensions::Event::WillDispatchCallback(),
+ delta.release());
data->OnChangedFired();
}
data->set_json(new_json.Pass());
@@ -1124,33 +1411,42 @@ void ExtensionDownloadsEventRouter::OnDownloadRemoved(
if (download_item->IsTemporary())
return;
DispatchEvent(extensions::event_names::kOnDownloadErased,
+ true,
+ extensions::Event::WillDispatchCallback(),
base::Value::CreateIntegerValue(download_item->GetId()));
}
void ExtensionDownloadsEventRouter::DispatchEvent(
- const char* event_name, base::Value* arg) {
+ const char* event_name,
+ bool include_incognito,
+ const extensions::Event::WillDispatchCallback& will_dispatch_callback,
+ 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_;
+ event->will_dispatch_callback = will_dispatch_callback;
+ 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(
+ &notification_source);
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
+ content_source,
+ content::Details<std::string>(&json_args));
}

Powered by Google App Engine
This is Rietveld 408576698