| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/api/downloads/downloads_api.h" |
| |
| #include <algorithm> |
| #include <cctype> |
| #include <iterator> |
| #include <set> |
| #include <string> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/file_util.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/strings/string16.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/download/download_danger_prompt.h" |
| #include "chrome/browser/download/download_file_icon_extractor.h" |
| #include "chrome/browser/download/download_prefs.h" |
| #include "chrome/browser/download/download_query.h" |
| #include "chrome/browser/download/download_service.h" |
| #include "chrome/browser/download/download_service_factory.h" |
| #include "chrome/browser/download/download_shelf.h" |
| #include "chrome/browser/download/download_stats.h" |
| #include "chrome/browser/download/drag_download_item.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/extensions/extension_warning_service.h" |
| #include "chrome/browser/extensions/extension_warning_set.h" |
| #include "chrome/browser/icon_loader.h" |
| #include "chrome/browser/icon_manager.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/renderer_host/chrome_render_message_filter.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/common/cancelable_task_tracker.h" |
| #include "chrome/common/extensions/api/downloads.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/permissions/permissions_data.h" |
| #include "components/web_modal/web_contents_modal_dialog_manager.h" |
| #include "content/public/browser/download_interrupt_reasons.h" |
| #include "content/public/browser/download_item.h" |
| #include "content/public/browser/download_save_info.h" |
| #include "content/public/browser/download_url_parameters.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/resource_context.h" |
| #include "content/public/browser/resource_dispatcher_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_view.h" |
| #include "content/public/browser/web_contents_view.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_util.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_request.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/webui/web_ui_util.h" |
| |
| using content::BrowserContext; |
| using content::BrowserThread; |
| using content::DownloadItem; |
| using content::DownloadManager; |
| |
| namespace download_extension_errors { |
| |
| const char kEmptyFile[] = "Filename not yet determined"; |
| const char kFileAlreadyDeleted[] = "Download file already deleted"; |
| const char kIconNotFound[] = "Icon not found"; |
| const char kInvalidDangerType[] = "Invalid danger type"; |
| const char kInvalidFilename[] = "Invalid filename"; |
| const char kInvalidFilter[] = "Invalid query filter"; |
| const char kInvalidHeader[] = "Invalid request header"; |
| const char kInvalidId[] = "Invalid downloadId"; |
| const char kInvalidOrderBy[] = "Invalid orderBy field"; |
| const char kInvalidQueryLimit[] = "Invalid query limit"; |
| const char kInvalidState[] = "Invalid state"; |
| const char kInvalidURL[] = "Invalid URL"; |
| const char kInvisibleContext[] = "Javascript execution context is not visible " |
| "(tab, window, popup bubble)"; |
| const char kNotComplete[] = "Download must be complete"; |
| const char kNotDangerous[] = "Download must be dangerous"; |
| const char kNotInProgress[] = "Download must be in progress"; |
| const char kNotResumable[] = "DownloadItem.canResume must be true"; |
| const char kOpenPermission[] = "The \"downloads.open\" permission is required"; |
| const char kShelfDisabled[] = "Another extension has disabled the shelf"; |
| const char kShelfPermission[] = "downloads.setShelfEnabled requires the " |
| "\"downloads.shelf\" permission"; |
| const char kTooManyListeners[] = "Each extension may have at most one " |
| "onDeterminingFilename listener between all of its renderer execution " |
| "contexts."; |
| const char kUnexpectedDeterminer[] = "Unexpected determineFilename call"; |
| |
| } // namespace download_extension_errors |
| |
| namespace errors = download_extension_errors; |
| |
| namespace { |
| |
| namespace downloads = extensions::api::downloads; |
| |
| // Default icon size for getFileIcon() in pixels. |
| const int kDefaultIconSize = 32; |
| |
| // Parameter keys |
| const char kByExtensionIdKey[] = "byExtensionId"; |
| const char kByExtensionNameKey[] = "byExtensionName"; |
| const char kBytesReceivedKey[] = "bytesReceived"; |
| const char kCanResumeKey[] = "canResume"; |
| const char kDangerAccepted[] = "accepted"; |
| const char kDangerContent[] = "content"; |
| const char kDangerFile[] = "file"; |
| const char kDangerHost[] = "host"; |
| const char kDangerKey[] = "danger"; |
| const char kDangerSafe[] = "safe"; |
| const char kDangerUncommon[] = "uncommon"; |
| const char kDangerUnwanted[] = "unwanted"; |
| const char kDangerUrl[] = "url"; |
| const char kEndTimeKey[] = "endTime"; |
| const char kEndedAfterKey[] = "endedAfter"; |
| const char kEndedBeforeKey[] = "endedBefore"; |
| const char kErrorKey[] = "error"; |
| const char kEstimatedEndTimeKey[] = "estimatedEndTime"; |
| const char kExistsKey[] = "exists"; |
| const char kFileSizeKey[] = "fileSize"; |
| const char kFilenameKey[] = "filename"; |
| const char kFilenameRegexKey[] = "filenameRegex"; |
| const char kIdKey[] = "id"; |
| const char kIncognitoKey[] = "incognito"; |
| const char kMimeKey[] = "mime"; |
| const char kPausedKey[] = "paused"; |
| const char kQueryKey[] = "query"; |
| const char kReferrerUrlKey[] = "referrer"; |
| const char kStartTimeKey[] = "startTime"; |
| const char kStartedAfterKey[] = "startedAfter"; |
| const char kStartedBeforeKey[] = "startedBefore"; |
| const char kStateComplete[] = "complete"; |
| const char kStateInProgress[] = "in_progress"; |
| const char kStateInterrupted[] = "interrupted"; |
| const char kStateKey[] = "state"; |
| const char kTotalBytesGreaterKey[] = "totalBytesGreater"; |
| const char kTotalBytesKey[] = "totalBytes"; |
| const char kTotalBytesLessKey[] = "totalBytesLess"; |
| const char kUrlKey[] = "url"; |
| const char kUrlRegexKey[] = "urlRegex"; |
| |
| // Note: Any change to the danger type strings, should be accompanied by a |
| // corresponding change to downloads.json. |
| const char* kDangerStrings[] = { |
| kDangerSafe, |
| kDangerFile, |
| kDangerUrl, |
| kDangerContent, |
| kDangerSafe, |
| kDangerUncommon, |
| kDangerAccepted, |
| kDangerHost, |
| kDangerUnwanted |
| }; |
| COMPILE_ASSERT(arraysize(kDangerStrings) == content::DOWNLOAD_DANGER_TYPE_MAX, |
| download_danger_type_enum_changed); |
| |
| // Note: Any change to the state strings, should be accompanied by a |
| // corresponding change to downloads.json. |
| const char* kStateStrings[] = { |
| kStateInProgress, |
| kStateComplete, |
| kStateInterrupted, |
| kStateInterrupted, |
| }; |
| COMPILE_ASSERT(arraysize(kStateStrings) == DownloadItem::MAX_DOWNLOAD_STATE, |
| download_item_state_enum_changed); |
| |
| const char* DangerString(content::DownloadDangerType danger) { |
| DCHECK(danger >= 0); |
| DCHECK(danger < static_cast<content::DownloadDangerType>( |
| arraysize(kDangerStrings))); |
| if (danger < 0 || danger >= static_cast<content::DownloadDangerType>( |
| arraysize(kDangerStrings))) |
| return ""; |
| return kDangerStrings[danger]; |
| } |
| |
| content::DownloadDangerType DangerEnumFromString(const std::string& danger) { |
| for (size_t i = 0; i < arraysize(kDangerStrings); ++i) { |
| if (danger == kDangerStrings[i]) |
| return static_cast<content::DownloadDangerType>(i); |
| } |
| return content::DOWNLOAD_DANGER_TYPE_MAX; |
| } |
| |
| const char* StateString(DownloadItem::DownloadState state) { |
| DCHECK(state >= 0); |
| DCHECK(state < static_cast<DownloadItem::DownloadState>( |
| arraysize(kStateStrings))); |
| if (state < 0 || state >= static_cast<DownloadItem::DownloadState>( |
| arraysize(kStateStrings))) |
| return ""; |
| return kStateStrings[state]; |
| } |
| |
| DownloadItem::DownloadState StateEnumFromString(const std::string& state) { |
| for (size_t i = 0; i < arraysize(kStateStrings); ++i) { |
| if ((kStateStrings[i] != NULL) && (state == kStateStrings[i])) |
| return static_cast<DownloadItem::DownloadState>(i); |
| } |
| return DownloadItem::MAX_DOWNLOAD_STATE; |
| } |
| |
| std::string TimeToISO8601(const base::Time& t) { |
| base::Time::Exploded exploded; |
| t.UTCExplode(&exploded); |
| return base::StringPrintf( |
| "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month, |
| exploded.day_of_month, exploded.hour, exploded.minute, exploded.second, |
| exploded.millisecond); |
| } |
| |
| scoped_ptr<base::DictionaryValue> DownloadItemToJSON( |
| DownloadItem* download_item, |
| Profile* profile) { |
| base::DictionaryValue* json = new base::DictionaryValue(); |
| json->SetBoolean(kExistsKey, !download_item->GetFileExternallyRemoved()); |
| json->SetInteger(kIdKey, download_item->GetId()); |
| const GURL& url = download_item->GetOriginalUrl(); |
| json->SetString(kUrlKey, (url.is_valid() ? url.spec() : std::string())); |
| const GURL& referrer = download_item->GetReferrerUrl(); |
| json->SetString(kReferrerUrlKey, (referrer.is_valid() ? referrer.spec() |
| : std::string())); |
| json->SetString(kFilenameKey, |
| download_item->GetTargetFilePath().LossyDisplayName()); |
| json->SetString(kDangerKey, DangerString(download_item->GetDangerType())); |
| json->SetString(kStateKey, StateString(download_item->GetState())); |
| json->SetBoolean(kCanResumeKey, download_item->CanResume()); |
| json->SetBoolean(kPausedKey, download_item->IsPaused()); |
| json->SetString(kMimeKey, download_item->GetMimeType()); |
| json->SetString(kStartTimeKey, TimeToISO8601(download_item->GetStartTime())); |
| json->SetInteger(kBytesReceivedKey, download_item->GetReceivedBytes()); |
| json->SetInteger(kTotalBytesKey, download_item->GetTotalBytes()); |
| json->SetBoolean(kIncognitoKey, profile->IsOffTheRecord()); |
| if (download_item->GetState() == DownloadItem::INTERRUPTED) { |
| json->SetString(kErrorKey, content::InterruptReasonDebugString( |
| download_item->GetLastReason())); |
| } else if (download_item->GetState() == DownloadItem::CANCELLED) { |
| json->SetString(kErrorKey, content::InterruptReasonDebugString( |
| content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED)); |
| } |
| if (!download_item->GetEndTime().is_null()) |
| json->SetString(kEndTimeKey, TimeToISO8601(download_item->GetEndTime())); |
| base::TimeDelta time_remaining; |
| if (download_item->TimeRemaining(&time_remaining)) { |
| base::Time now = base::Time::Now(); |
| json->SetString(kEstimatedEndTimeKey, TimeToISO8601(now + time_remaining)); |
| } |
| DownloadedByExtension* by_ext = DownloadedByExtension::Get(download_item); |
| if (by_ext) { |
| json->SetString(kByExtensionIdKey, by_ext->id()); |
| json->SetString(kByExtensionNameKey, by_ext->name()); |
| // Lookup the extension's current name() in case the user changed their |
| // language. This won't work if the extension was uninstalled, so the name |
| // might be the wrong language. |
| bool include_disabled = true; |
| const extensions::Extension* extension = extensions::ExtensionSystem::Get( |
| profile)->extension_service()->GetExtensionById( |
| by_ext->id(), include_disabled); |
| if (extension) |
| json->SetString(kByExtensionNameKey, extension->name()); |
| } |
| // TODO(benjhayden): Implement fileSize. |
| json->SetInteger(kFileSizeKey, download_item->GetTotalBytes()); |
| return scoped_ptr<base::DictionaryValue>(json); |
| } |
| |
| class DownloadFileIconExtractorImpl : public DownloadFileIconExtractor { |
| public: |
| DownloadFileIconExtractorImpl() {} |
| |
| virtual ~DownloadFileIconExtractorImpl() {} |
| |
| virtual bool ExtractIconURLForPath(const base::FilePath& path, |
| IconLoader::IconSize icon_size, |
| IconURLCallback callback) OVERRIDE; |
| private: |
| void OnIconLoadComplete(gfx::Image* icon); |
| |
| CancelableTaskTracker cancelable_task_tracker_; |
| IconURLCallback callback_; |
| }; |
| |
| bool DownloadFileIconExtractorImpl::ExtractIconURLForPath( |
| const base::FilePath& path, |
| IconLoader::IconSize icon_size, |
| IconURLCallback callback) { |
| callback_ = callback; |
| 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, always call LoadIcon instead of attempting a LookupIcon. |
| im->LoadIcon(path, |
| icon_size, |
| base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete, |
| base::Unretained(this)), |
| &cancelable_task_tracker_); |
| return true; |
| } |
| |
| void DownloadFileIconExtractorImpl::OnIconLoadComplete(gfx::Image* icon) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| std::string url; |
| if (icon) |
| url = webui::GetBitmapDataUrl(icon->AsBitmap()); |
| callback_.Run(url); |
| } |
| |
| IconLoader::IconSize IconLoaderSizeFromPixelSize(int pixel_size) { |
| switch (pixel_size) { |
| case 16: return IconLoader::SMALL; |
| case 32: return IconLoader::NORMAL; |
| default: |
| NOTREACHED(); |
| return IconLoader::NORMAL; |
| } |
| } |
| |
| typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap; |
| |
| void InitFilterTypeMap(FilterTypeMap& filter_types) { |
| filter_types[kBytesReceivedKey] = DownloadQuery::FILTER_BYTES_RECEIVED; |
| filter_types[kExistsKey] = DownloadQuery::FILTER_EXISTS; |
| filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME; |
| filter_types[kFilenameRegexKey] = DownloadQuery::FILTER_FILENAME_REGEX; |
| filter_types[kMimeKey] = DownloadQuery::FILTER_MIME; |
| filter_types[kPausedKey] = DownloadQuery::FILTER_PAUSED; |
| filter_types[kQueryKey] = DownloadQuery::FILTER_QUERY; |
| filter_types[kEndedAfterKey] = DownloadQuery::FILTER_ENDED_AFTER; |
| filter_types[kEndedBeforeKey] = DownloadQuery::FILTER_ENDED_BEFORE; |
| filter_types[kEndTimeKey] = DownloadQuery::FILTER_END_TIME; |
| filter_types[kStartedAfterKey] = DownloadQuery::FILTER_STARTED_AFTER; |
| filter_types[kStartedBeforeKey] = DownloadQuery::FILTER_STARTED_BEFORE; |
| filter_types[kStartTimeKey] = DownloadQuery::FILTER_START_TIME; |
| filter_types[kTotalBytesKey] = DownloadQuery::FILTER_TOTAL_BYTES; |
| filter_types[kTotalBytesGreaterKey] = |
| DownloadQuery::FILTER_TOTAL_BYTES_GREATER; |
| filter_types[kTotalBytesLessKey] = DownloadQuery::FILTER_TOTAL_BYTES_LESS; |
| filter_types[kUrlKey] = DownloadQuery::FILTER_URL; |
| filter_types[kUrlRegexKey] = DownloadQuery::FILTER_URL_REGEX; |
| } |
| |
| typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap; |
| |
| void InitSortTypeMap(SortTypeMap& sorter_types) { |
| sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED; |
| sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER; |
| sorter_types[kEndTimeKey] = DownloadQuery::SORT_END_TIME; |
| sorter_types[kExistsKey] = DownloadQuery::SORT_EXISTS; |
| sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME; |
| sorter_types[kMimeKey] = DownloadQuery::SORT_MIME; |
| sorter_types[kPausedKey] = DownloadQuery::SORT_PAUSED; |
| sorter_types[kStartTimeKey] = DownloadQuery::SORT_START_TIME; |
| sorter_types[kStateKey] = DownloadQuery::SORT_STATE; |
| sorter_types[kTotalBytesKey] = DownloadQuery::SORT_TOTAL_BYTES; |
| sorter_types[kUrlKey] = DownloadQuery::SORT_URL; |
| } |
| |
| bool IsNotTemporaryDownloadFilter(const DownloadItem& download_item) { |
| return !download_item.IsTemporary(); |
| } |
| |
| // Set |manager| to the on-record DownloadManager, and |incognito_manager| to |
| // the off-record DownloadManager if one exists and is requested via |
| // |include_incognito|. This should work regardless of whether |profile| is |
| // original or incognito. |
| void GetManagers( |
| Profile* profile, |
| bool include_incognito, |
| DownloadManager** manager, |
| DownloadManager** incognito_manager) { |
| *manager = BrowserContext::GetDownloadManager(profile->GetOriginalProfile()); |
| if (profile->HasOffTheRecordProfile() && |
| (include_incognito || |
| profile->IsOffTheRecord())) { |
| *incognito_manager = BrowserContext::GetDownloadManager( |
| profile->GetOffTheRecordProfile()); |
| } else { |
| *incognito_manager = NULL; |
| } |
| } |
| |
| DownloadItem* GetDownload(Profile* profile, bool include_incognito, int id) { |
| DownloadManager* manager = NULL; |
| DownloadManager* incognito_manager = NULL; |
| GetManagers(profile, include_incognito, &manager, &incognito_manager); |
| DownloadItem* download_item = manager->GetDownload(id); |
| if (!download_item && incognito_manager) |
| download_item = incognito_manager->GetDownload(id); |
| return download_item; |
| } |
| |
| enum DownloadsFunctionName { |
| DOWNLOADS_FUNCTION_DOWNLOAD = 0, |
| DOWNLOADS_FUNCTION_SEARCH = 1, |
| DOWNLOADS_FUNCTION_PAUSE = 2, |
| DOWNLOADS_FUNCTION_RESUME = 3, |
| DOWNLOADS_FUNCTION_CANCEL = 4, |
| DOWNLOADS_FUNCTION_ERASE = 5, |
| // 6 unused |
| DOWNLOADS_FUNCTION_ACCEPT_DANGER = 7, |
| DOWNLOADS_FUNCTION_SHOW = 8, |
| DOWNLOADS_FUNCTION_DRAG = 9, |
| DOWNLOADS_FUNCTION_GET_FILE_ICON = 10, |
| DOWNLOADS_FUNCTION_OPEN = 11, |
| DOWNLOADS_FUNCTION_REMOVE_FILE = 12, |
| DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER = 13, |
| DOWNLOADS_FUNCTION_SET_SHELF_ENABLED = 14, |
| // Insert new values here, not at the beginning. |
| DOWNLOADS_FUNCTION_LAST |
| }; |
| |
| void RecordApiFunctions(DownloadsFunctionName function) { |
| UMA_HISTOGRAM_ENUMERATION("Download.ApiFunctions", |
| function, |
| DOWNLOADS_FUNCTION_LAST); |
| } |
| |
| void CompileDownloadQueryOrderBy( |
| const std::vector<std::string>& order_by_strs, |
| std::string* error, |
| DownloadQuery* query) { |
| // TODO(benjhayden): Consider switching from LazyInstance to explicit string |
| // comparisons. |
| static base::LazyInstance<SortTypeMap> sorter_types = |
| LAZY_INSTANCE_INITIALIZER; |
| if (sorter_types.Get().size() == 0) |
| InitSortTypeMap(sorter_types.Get()); |
| |
| for (std::vector<std::string>::const_iterator iter = order_by_strs.begin(); |
| iter != order_by_strs.end(); ++iter) { |
| std::string term_str = *iter; |
| if (term_str.empty()) |
| continue; |
| DownloadQuery::SortDirection direction = DownloadQuery::ASCENDING; |
| if (term_str[0] == '-') { |
| direction = DownloadQuery::DESCENDING; |
| term_str = term_str.substr(1); |
| } |
| SortTypeMap::const_iterator sorter_type = |
| sorter_types.Get().find(term_str); |
| if (sorter_type == sorter_types.Get().end()) { |
| *error = errors::kInvalidOrderBy; |
| return; |
| } |
| query->AddSorter(sorter_type->second, direction); |
| } |
| } |
| |
| void RunDownloadQuery( |
| const downloads::DownloadQuery& query_in, |
| DownloadManager* manager, |
| DownloadManager* incognito_manager, |
| std::string* error, |
| DownloadQuery::DownloadVector* results) { |
| // TODO(benjhayden): Consider switching from LazyInstance to explicit string |
| // comparisons. |
| static base::LazyInstance<FilterTypeMap> filter_types = |
| LAZY_INSTANCE_INITIALIZER; |
| if (filter_types.Get().size() == 0) |
| InitFilterTypeMap(filter_types.Get()); |
| |
| DownloadQuery query_out; |
| |
| size_t limit = 1000; |
| if (query_in.limit.get()) { |
| if (*query_in.limit.get() < 0) { |
| *error = errors::kInvalidQueryLimit; |
| return; |
| } |
| limit = *query_in.limit.get(); |
| } |
| if (limit > 0) { |
| query_out.Limit(limit); |
| } |
| |
| std::string state_string = |
| downloads::ToString(query_in.state); |
| if (!state_string.empty()) { |
| DownloadItem::DownloadState state = StateEnumFromString(state_string); |
| if (state == DownloadItem::MAX_DOWNLOAD_STATE) { |
| *error = errors::kInvalidState; |
| return; |
| } |
| query_out.AddFilter(state); |
| } |
| std::string danger_string = |
| downloads::ToString(query_in.danger); |
| if (!danger_string.empty()) { |
| content::DownloadDangerType danger_type = DangerEnumFromString( |
| danger_string); |
| if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) { |
| *error = errors::kInvalidDangerType; |
| return; |
| } |
| query_out.AddFilter(danger_type); |
| } |
| if (query_in.order_by.get()) { |
| CompileDownloadQueryOrderBy(*query_in.order_by.get(), error, &query_out); |
| if (!error->empty()) |
| return; |
| } |
| |
| scoped_ptr<base::DictionaryValue> query_in_value(query_in.ToValue().Pass()); |
| for (base::DictionaryValue::Iterator query_json_field(*query_in_value.get()); |
| !query_json_field.IsAtEnd(); query_json_field.Advance()) { |
| FilterTypeMap::const_iterator filter_type = |
| filter_types.Get().find(query_json_field.key()); |
| if (filter_type != filter_types.Get().end()) { |
| if (!query_out.AddFilter(filter_type->second, query_json_field.value())) { |
| *error = errors::kInvalidFilter; |
| return; |
| } |
| } |
| } |
| |
| DownloadQuery::DownloadVector all_items; |
| if (query_in.id.get()) { |
| DownloadItem* download_item = manager->GetDownload(*query_in.id.get()); |
| if (!download_item && incognito_manager) |
| download_item = incognito_manager->GetDownload(*query_in.id.get()); |
| if (download_item) |
| all_items.push_back(download_item); |
| } else { |
| manager->GetAllDownloads(&all_items); |
| if (incognito_manager) |
| incognito_manager->GetAllDownloads(&all_items); |
| } |
| query_out.AddFilter(base::Bind(&IsNotTemporaryDownloadFilter)); |
| query_out.Search(all_items.begin(), all_items.end(), results); |
| } |
| |
| DownloadPathReservationTracker::FilenameConflictAction ConvertConflictAction( |
| downloads::FilenameConflictAction action) { |
| switch (action) { |
| case downloads::FILENAME_CONFLICT_ACTION_NONE: |
| case downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY: |
| return DownloadPathReservationTracker::UNIQUIFY; |
| case downloads::FILENAME_CONFLICT_ACTION_OVERWRITE: |
| return DownloadPathReservationTracker::OVERWRITE; |
| case downloads::FILENAME_CONFLICT_ACTION_PROMPT: |
| return DownloadPathReservationTracker::PROMPT; |
| } |
| NOTREACHED(); |
| return DownloadPathReservationTracker::UNIQUIFY; |
| } |
| |
| class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data { |
| public: |
| static ExtensionDownloadsEventRouterData* Get(DownloadItem* download_item) { |
| base::SupportsUserData::Data* data = download_item->GetUserData(kKey); |
| return (data == NULL) ? NULL : |
| static_cast<ExtensionDownloadsEventRouterData*>(data); |
| } |
| |
| static void Remove(DownloadItem* download_item) { |
| download_item->RemoveUserData(kKey); |
| } |
| |
| explicit ExtensionDownloadsEventRouterData( |
| DownloadItem* download_item, |
| scoped_ptr<base::DictionaryValue> json_item) |
| : updated_(0), |
| changed_fired_(0), |
| json_(json_item.Pass()), |
| creator_conflict_action_( |
| downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY), |
| determined_conflict_action_( |
| downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| download_item->SetUserData(kKey, this); |
| } |
| |
| virtual ~ExtensionDownloadsEventRouterData() { |
| if (updated_ > 0) { |
| UMA_HISTOGRAM_PERCENTAGE("Download.OnChanged", |
| (changed_fired_ * 100 / updated_)); |
| } |
| } |
| |
| const base::DictionaryValue& json() const { return *json_.get(); } |
| void set_json(scoped_ptr<base::DictionaryValue> json_item) { |
| json_ = json_item.Pass(); |
| } |
| |
| void OnItemUpdated() { ++updated_; } |
| void OnChangedFired() { ++changed_fired_; } |
| |
| void set_filename_change_callbacks( |
| const base::Closure& no_change, |
| const ExtensionDownloadsEventRouter::FilenameChangedCallback& change) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| filename_no_change_ = no_change; |
| filename_change_ = change; |
| determined_filename_ = creator_suggested_filename_; |
| determined_conflict_action_ = creator_conflict_action_; |
| // determiner_.install_time should default to 0 so that creator suggestions |
| // should be lower priority than any actual onDeterminingFilename listeners. |
| } |
| |
| void ClearPendingDeterminers() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| determined_filename_.clear(); |
| determined_conflict_action_ = |
| downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY; |
| determiner_ = DeterminerInfo(); |
| filename_no_change_ = base::Closure(); |
| filename_change_ = ExtensionDownloadsEventRouter::FilenameChangedCallback(); |
| weak_ptr_factory_.reset(); |
| determiners_.clear(); |
| } |
| |
| void DeterminerRemoved(const std::string& extension_id) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 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) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 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) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| for (size_t index = 0; index < determiners_.size(); ++index) { |
| if (determiners_[index].extension_id == extension_id) { |
| return determiners_[index].reported; |
| } |
| } |
| return false; |
| } |
| |
| void CreatorSuggestedFilename( |
| const base::FilePath& filename, |
| downloads::FilenameConflictAction conflict_action) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| creator_suggested_filename_ = filename; |
| creator_conflict_action_ = conflict_action; |
| } |
| |
| base::FilePath creator_suggested_filename() const { |
| return creator_suggested_filename_; |
| } |
| |
| downloads::FilenameConflictAction |
| creator_conflict_action() const { |
| return creator_conflict_action_; |
| } |
| |
| void ResetCreatorSuggestion() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| creator_suggested_filename_.clear(); |
| creator_conflict_action_ = |
| downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY; |
| } |
| |
| // Returns false if this |extension_id| was not expected or if this |
| // |extension_id| has already reported. The caller is responsible for |
| // validating |filename|. |
| bool DeterminerCallback( |
| Profile* profile, |
| const std::string& extension_id, |
| const base::FilePath& filename, |
| downloads::FilenameConflictAction conflict_action) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 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 (!filename.empty()) { |
| extensions::ExtensionWarningSet warnings; |
| std::string winner_extension_id; |
| ExtensionDownloadsEventRouter::DetermineFilenameInternal( |
| filename, |
| conflict_action, |
| determiners_[index].extension_id, |
| determiners_[index].install_time, |
| determiner_.extension_id, |
| determiner_.install_time, |
| &winner_extension_id, |
| &determined_filename_, |
| &determined_conflict_action_, |
| &warnings); |
| if (!warnings.empty()) |
| extensions::ExtensionWarningService::NotifyWarningsOnUI( |
| profile, warnings); |
| if (winner_extension_id == determiners_[index].extension_id) |
| 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_, ConvertConflictAction( |
| determined_conflict_action_)); |
| } |
| } |
| // Don't clear determiners_ immediately in case there's a second listener |
| // for one of the extensions, so that DetermineFilename can return |
| // kTooManyListeners. After a few seconds, DetermineFilename will return |
| // kUnexpectedDeterminer instead of kTooManyListeners so that determiners_ |
| // doesn't keep hogging memory. |
| weak_ptr_factory_.reset( |
| new base::WeakPtrFactory<ExtensionDownloadsEventRouterData>(this)); |
| base::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 creator_suggested_filename_; |
| downloads::FilenameConflictAction |
| creator_conflict_action_; |
| base::FilePath determined_filename_; |
| downloads::FilenameConflictAction |
| determined_conflict_action_; |
| 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"; |
| |
| class ManagerDestructionObserver : public DownloadManager::Observer { |
| public: |
| static void CheckForHistoryFilesRemoval(DownloadManager* manager) { |
| if (!manager) |
| return; |
| if (!manager_file_existence_last_checked_) |
| manager_file_existence_last_checked_ = |
| new std::map<DownloadManager*, ManagerDestructionObserver*>(); |
| if (!(*manager_file_existence_last_checked_)[manager]) |
| (*manager_file_existence_last_checked_)[manager] = |
| new ManagerDestructionObserver(manager); |
| (*manager_file_existence_last_checked_)[manager]-> |
| CheckForHistoryFilesRemovalInternal(); |
| } |
| |
| private: |
| static const int kFileExistenceRateLimitSeconds = 10; |
| |
| explicit ManagerDestructionObserver(DownloadManager* manager) |
| : manager_(manager) { |
| manager_->AddObserver(this); |
| } |
| |
| virtual ~ManagerDestructionObserver() { |
| manager_->RemoveObserver(this); |
| } |
| |
| virtual void ManagerGoingDown(DownloadManager* manager) OVERRIDE { |
| manager_file_existence_last_checked_->erase(manager); |
| if (manager_file_existence_last_checked_->size() == 0) { |
| delete manager_file_existence_last_checked_; |
| manager_file_existence_last_checked_ = NULL; |
| } |
| } |
| |
| void CheckForHistoryFilesRemovalInternal() { |
| base::Time now(base::Time::Now()); |
| int delta = now.ToTimeT() - last_checked_.ToTimeT(); |
| if (delta > kFileExistenceRateLimitSeconds) { |
| last_checked_ = now; |
| manager_->CheckForHistoryFilesRemoval(); |
| } |
| } |
| |
| static std::map<DownloadManager*, ManagerDestructionObserver*>* |
| manager_file_existence_last_checked_; |
| |
| DownloadManager* manager_; |
| base::Time last_checked_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ManagerDestructionObserver); |
| }; |
| |
| std::map<DownloadManager*, ManagerDestructionObserver*>* |
| ManagerDestructionObserver::manager_file_existence_last_checked_ = NULL; |
| |
| void OnDeterminingFilenameWillDispatchCallback( |
| bool* any_determiners, |
| ExtensionDownloadsEventRouterData* data, |
| Profile* profile, |
| const extensions::Extension* extension, |
| base::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); |
| } |
| |
| bool Fault(bool error, |
| const char* message_in, |
| std::string* message_out) { |
| if (!error) |
| return false; |
| *message_out = message_in; |
| return true; |
| } |
| |
| bool InvalidId(DownloadItem* valid_item, std::string* message_out) { |
| return Fault(!valid_item, errors::kInvalidId, message_out); |
| } |
| |
| bool IsDownloadDeltaField(const std::string& field) { |
| return ((field == kUrlKey) || |
| (field == kFilenameKey) || |
| (field == kDangerKey) || |
| (field == kMimeKey) || |
| (field == kStartTimeKey) || |
| (field == kEndTimeKey) || |
| (field == kStateKey) || |
| (field == kCanResumeKey) || |
| (field == kPausedKey) || |
| (field == kErrorKey) || |
| (field == kTotalBytesKey) || |
| (field == kFileSizeKey) || |
| (field == kExistsKey)); |
| } |
| |
| } // namespace |
| |
| const char DownloadedByExtension::kKey[] = |
| "DownloadItem DownloadedByExtension"; |
| |
| DownloadedByExtension* DownloadedByExtension::Get( |
| content::DownloadItem* item) { |
| base::SupportsUserData::Data* data = item->GetUserData(kKey); |
| return (data == NULL) ? NULL : |
| static_cast<DownloadedByExtension*>(data); |
| } |
| |
| DownloadedByExtension::DownloadedByExtension( |
| content::DownloadItem* item, |
| const std::string& id, |
| const std::string& name) |
| : id_(id), |
| name_(name) { |
| item->SetUserData(kKey, this); |
| } |
| |
| DownloadsDownloadFunction::DownloadsDownloadFunction() {} |
| |
| DownloadsDownloadFunction::~DownloadsDownloadFunction() {} |
| |
| bool DownloadsDownloadFunction::RunImpl() { |
| scoped_ptr<downloads::Download::Params> params( |
| downloads::Download::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| const downloads::DownloadOptions& options = params->options; |
| GURL download_url(options.url); |
| if (Fault(!download_url.is_valid(), errors::kInvalidURL, &error_)) |
| return false; |
| |
| Profile* current_profile = GetProfile(); |
| if (include_incognito() && GetProfile()->HasOffTheRecordProfile()) |
| current_profile = GetProfile()->GetOffTheRecordProfile(); |
| |
| scoped_ptr<content::DownloadUrlParameters> download_params( |
| new content::DownloadUrlParameters( |
| download_url, |
| render_view_host()->GetProcess()->GetID(), |
| render_view_host()->GetRoutingID(), |
| current_profile->GetResourceContext())); |
| |
| base::FilePath creator_suggested_filename; |
| if (options.filename.get()) { |
| #if defined(OS_WIN) |
| // Can't get filename16 from options.ToValue() because that converts it from |
| // std::string. |
| base::DictionaryValue* options_value = NULL; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options_value)); |
| base::string16 filename16; |
| EXTENSION_FUNCTION_VALIDATE(options_value->GetString( |
| kFilenameKey, &filename16)); |
| creator_suggested_filename = base::FilePath(filename16); |
| #elif defined(OS_POSIX) |
| creator_suggested_filename = base::FilePath(*options.filename.get()); |
| #endif |
| if (!net::IsSafePortableRelativePath(creator_suggested_filename)) { |
| error_ = errors::kInvalidFilename; |
| return false; |
| } |
| } |
| |
| if (options.save_as.get()) |
| download_params->set_prompt(*options.save_as.get()); |
| |
| if (options.headers.get()) { |
| typedef downloads::HeaderNameValuePair HeaderNameValuePair; |
| for (std::vector<linked_ptr<HeaderNameValuePair> >::const_iterator iter = |
| options.headers->begin(); |
| iter != options.headers->end(); |
| ++iter) { |
| const HeaderNameValuePair& name_value = **iter; |
| if (!net::HttpUtil::IsSafeHeader(name_value.name)) { |
| error_ = errors::kInvalidHeader; |
| return false; |
| } |
| download_params->add_request_header(name_value.name, name_value.value); |
| } |
| } |
| |
| std::string method_string = |
| downloads::ToString(options.method); |
| if (!method_string.empty()) |
| download_params->set_method(method_string); |
| if (options.body.get()) |
| download_params->set_post_body(*options.body.get()); |
| download_params->set_callback(base::Bind( |
| &DownloadsDownloadFunction::OnStarted, this, |
| creator_suggested_filename, options.conflict_action)); |
| // Prevent login prompts for 401/407 responses. |
| download_params->set_load_flags(net::LOAD_DO_NOT_PROMPT_FOR_LOGIN); |
| |
| DownloadManager* manager = BrowserContext::GetDownloadManager( |
| current_profile); |
| manager->DownloadUrl(download_params.Pass()); |
| RecordDownloadSource(DOWNLOAD_INITIATED_BY_EXTENSION); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_DOWNLOAD); |
| return true; |
| } |
| |
| void DownloadsDownloadFunction::OnStarted( |
| const base::FilePath& creator_suggested_filename, |
| downloads::FilenameConflictAction creator_conflict_action, |
| DownloadItem* item, |
| net::Error error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| VLOG(1) << __FUNCTION__ << " " << item << " " << error; |
| if (item) { |
| DCHECK_EQ(net::OK, error); |
| SetResult(new base::FundamentalValue(static_cast<int>(item->GetId()))); |
| if (!creator_suggested_filename.empty()) { |
| ExtensionDownloadsEventRouterData* data = |
| ExtensionDownloadsEventRouterData::Get(item); |
| if (!data) { |
| data = new ExtensionDownloadsEventRouterData( |
| item, |
| scoped_ptr<base::DictionaryValue>(new base::DictionaryValue())); |
| } |
| data->CreatorSuggestedFilename( |
| creator_suggested_filename, creator_conflict_action); |
| } |
| new DownloadedByExtension( |
| item, GetExtension()->id(), GetExtension()->name()); |
| item->UpdateObservers(); |
| } else { |
| DCHECK_NE(net::OK, error); |
| error_ = net::ErrorToString(error); |
| } |
| SendResponse(error_.empty()); |
| } |
| |
| DownloadsSearchFunction::DownloadsSearchFunction() {} |
| |
| DownloadsSearchFunction::~DownloadsSearchFunction() {} |
| |
| bool DownloadsSearchFunction::RunImpl() { |
| scoped_ptr<downloads::Search::Params> params( |
| downloads::Search::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadManager* manager = NULL; |
| DownloadManager* incognito_manager = NULL; |
| GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager); |
| ManagerDestructionObserver::CheckForHistoryFilesRemoval(manager); |
| ManagerDestructionObserver::CheckForHistoryFilesRemoval(incognito_manager); |
| DownloadQuery::DownloadVector results; |
| RunDownloadQuery(params->query, |
| manager, |
| incognito_manager, |
| &error_, |
| &results); |
| if (!error_.empty()) |
| return false; |
| |
| base::ListValue* json_results = new base::ListValue(); |
| for (DownloadManager::DownloadVector::const_iterator it = results.begin(); |
| it != results.end(); ++it) { |
| DownloadItem* download_item = *it; |
| uint32 download_id = download_item->GetId(); |
| bool off_record = ((incognito_manager != NULL) && |
| (incognito_manager->GetDownload(download_id) != NULL)); |
| scoped_ptr<base::DictionaryValue> json_item( |
| DownloadItemToJSON(*it, |
| off_record ? GetProfile()->GetOffTheRecordProfile() |
| : GetProfile()->GetOriginalProfile())); |
| json_results->Append(json_item.release()); |
| } |
| SetResult(json_results); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_SEARCH); |
| return true; |
| } |
| |
| DownloadsPauseFunction::DownloadsPauseFunction() {} |
| |
| DownloadsPauseFunction::~DownloadsPauseFunction() {} |
| |
| bool DownloadsPauseFunction::RunImpl() { |
| scoped_ptr<downloads::Pause::Params> params( |
| downloads::Pause::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (InvalidId(download_item, &error_) || |
| Fault(download_item->GetState() != DownloadItem::IN_PROGRESS, |
| errors::kNotInProgress, &error_)) |
| return false; |
| // If the item is already paused, this is a no-op and the operation will |
| // silently succeed. |
| download_item->Pause(); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_PAUSE); |
| return true; |
| } |
| |
| DownloadsResumeFunction::DownloadsResumeFunction() {} |
| |
| DownloadsResumeFunction::~DownloadsResumeFunction() {} |
| |
| bool DownloadsResumeFunction::RunImpl() { |
| scoped_ptr<downloads::Resume::Params> params( |
| downloads::Resume::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (InvalidId(download_item, &error_) || |
| Fault(download_item->IsPaused() && !download_item->CanResume(), |
| errors::kNotResumable, &error_)) |
| return false; |
| // Note that if the item isn't paused, this will be a no-op, and the extension |
| // call will seem successful. |
| download_item->Resume(); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_RESUME); |
| return true; |
| } |
| |
| DownloadsCancelFunction::DownloadsCancelFunction() {} |
| |
| DownloadsCancelFunction::~DownloadsCancelFunction() {} |
| |
| bool DownloadsCancelFunction::RunImpl() { |
| scoped_ptr<downloads::Resume::Params> params( |
| downloads::Resume::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (download_item && |
| (download_item->GetState() == DownloadItem::IN_PROGRESS)) |
| 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, it's not a failure. |
| RecordApiFunctions(DOWNLOADS_FUNCTION_CANCEL); |
| return true; |
| } |
| |
| DownloadsEraseFunction::DownloadsEraseFunction() {} |
| |
| DownloadsEraseFunction::~DownloadsEraseFunction() {} |
| |
| bool DownloadsEraseFunction::RunImpl() { |
| scoped_ptr<downloads::Erase::Params> params( |
| downloads::Erase::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadManager* manager = NULL; |
| DownloadManager* incognito_manager = NULL; |
| GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager); |
| DownloadQuery::DownloadVector results; |
| RunDownloadQuery(params->query, |
| manager, |
| incognito_manager, |
| &error_, |
| &results); |
| if (!error_.empty()) |
| return false; |
| base::ListValue* json_results = new base::ListValue(); |
| for (DownloadManager::DownloadVector::const_iterator it = results.begin(); |
| it != results.end(); ++it) { |
| json_results->Append( |
| new base::FundamentalValue(static_cast<int>((*it)->GetId()))); |
| (*it)->Remove(); |
| } |
| SetResult(json_results); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_ERASE); |
| return true; |
| } |
| |
| DownloadsRemoveFileFunction::DownloadsRemoveFileFunction() {} |
| |
| DownloadsRemoveFileFunction::~DownloadsRemoveFileFunction() { |
| if (item_) { |
| item_->RemoveObserver(this); |
| item_ = NULL; |
| } |
| } |
| |
| bool DownloadsRemoveFileFunction::RunImpl() { |
| scoped_ptr<downloads::RemoveFile::Params> params( |
| downloads::RemoveFile::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (InvalidId(download_item, &error_) || |
| Fault((download_item->GetState() != DownloadItem::COMPLETE), |
| errors::kNotComplete, &error_) || |
| Fault(download_item->GetFileExternallyRemoved(), |
| errors::kFileAlreadyDeleted, &error_)) |
| return false; |
| item_ = download_item; |
| item_->AddObserver(this); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_REMOVE_FILE); |
| download_item->DeleteFile(); |
| return true; |
| } |
| |
| void DownloadsRemoveFileFunction::OnDownloadUpdated(DownloadItem* download) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK_EQ(item_, download); |
| if (!item_->GetFileExternallyRemoved()) |
| return; |
| item_->RemoveObserver(this); |
| item_ = NULL; |
| SendResponse(true); |
| } |
| |
| void DownloadsRemoveFileFunction::OnDownloadDestroyed(DownloadItem* download) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK_EQ(item_, download); |
| item_->RemoveObserver(this); |
| item_ = NULL; |
| SendResponse(true); |
| } |
| |
| DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() {} |
| |
| DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {} |
| |
| DownloadsAcceptDangerFunction::OnPromptCreatedCallback* |
| DownloadsAcceptDangerFunction::on_prompt_created_ = NULL; |
| |
| bool DownloadsAcceptDangerFunction::RunImpl() { |
| scoped_ptr<downloads::AcceptDanger::Params> params( |
| downloads::AcceptDanger::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| PromptOrWait(params->download_id, 10); |
| return true; |
| } |
| |
| void DownloadsAcceptDangerFunction::PromptOrWait(int download_id, int retries) { |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), download_id); |
| content::WebContents* web_contents = |
| dispatcher()->delegate()->GetVisibleWebContents(); |
| if (InvalidId(download_item, &error_) || |
| Fault(download_item->GetState() != DownloadItem::IN_PROGRESS, |
| errors::kNotInProgress, &error_) || |
| Fault(!download_item->IsDangerous(), errors::kNotDangerous, &error_) || |
| Fault(!web_contents, errors::kInvisibleContext, &error_)) { |
| SendResponse(error_.empty()); |
| return; |
| } |
| bool visible = platform_util::IsVisible( |
| web_contents->GetView()->GetNativeView()); |
| if (!visible) { |
| if (retries > 0) { |
| base::MessageLoopForUI::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&DownloadsAcceptDangerFunction::PromptOrWait, |
| this, download_id, retries - 1), |
| base::TimeDelta::FromMilliseconds(100)); |
| return; |
| } |
| error_ = errors::kInvisibleContext; |
| SendResponse(error_.empty()); |
| return; |
| } |
| RecordApiFunctions(DOWNLOADS_FUNCTION_ACCEPT_DANGER); |
| // DownloadDangerPrompt displays a modal dialog using native widgets that the |
| // user must either accept or cancel. It cannot be scripted. |
| DownloadDangerPrompt* prompt = DownloadDangerPrompt::Create( |
| download_item, |
| web_contents, |
| true, |
| base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback, |
| this, download_id)); |
| // DownloadDangerPrompt deletes itself |
| if (on_prompt_created_ && !on_prompt_created_->is_null()) |
| on_prompt_created_->Run(prompt); |
| SendResponse(error_.empty()); |
| } |
| |
| void DownloadsAcceptDangerFunction::DangerPromptCallback( |
| int download_id, DownloadDangerPrompt::Action action) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), download_id); |
| if (InvalidId(download_item, &error_) || |
| Fault(download_item->GetState() != DownloadItem::IN_PROGRESS, |
| errors::kNotInProgress, &error_)) |
| return; |
| switch (action) { |
| case DownloadDangerPrompt::ACCEPT: |
| download_item->ValidateDangerousDownload(); |
| break; |
| case DownloadDangerPrompt::CANCEL: |
| download_item->Remove(); |
| break; |
| case DownloadDangerPrompt::DISMISS: |
| break; |
| } |
| SendResponse(error_.empty()); |
| } |
| |
| DownloadsShowFunction::DownloadsShowFunction() {} |
| |
| DownloadsShowFunction::~DownloadsShowFunction() {} |
| |
| bool DownloadsShowFunction::RunImpl() { |
| scoped_ptr<downloads::Show::Params> params( |
| downloads::Show::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (InvalidId(download_item, &error_)) |
| return false; |
| download_item->ShowDownloadInShell(); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW); |
| return true; |
| } |
| |
| DownloadsShowDefaultFolderFunction::DownloadsShowDefaultFolderFunction() {} |
| |
| DownloadsShowDefaultFolderFunction::~DownloadsShowDefaultFolderFunction() {} |
| |
| bool DownloadsShowDefaultFolderFunction::RunImpl() { |
| DownloadManager* manager = NULL; |
| DownloadManager* incognito_manager = NULL; |
| GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager); |
| platform_util::OpenItem(DownloadPrefs::FromDownloadManager( |
| manager)->DownloadPath()); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER); |
| return true; |
| } |
| |
| DownloadsOpenFunction::DownloadsOpenFunction() {} |
| |
| DownloadsOpenFunction::~DownloadsOpenFunction() {} |
| |
| bool DownloadsOpenFunction::RunImpl() { |
| scoped_ptr<downloads::Open::Params> params( |
| downloads::Open::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (InvalidId(download_item, &error_) || |
| Fault(download_item->GetState() != DownloadItem::COMPLETE, |
| errors::kNotComplete, &error_) || |
| Fault(!GetExtension()->HasAPIPermission( |
| extensions::APIPermission::kDownloadsOpen), |
| errors::kOpenPermission, &error_)) |
| return false; |
| download_item->OpenDownload(); |
| RecordApiFunctions(DOWNLOADS_FUNCTION_OPEN); |
| return true; |
| } |
| |
| DownloadsDragFunction::DownloadsDragFunction() {} |
| |
| DownloadsDragFunction::~DownloadsDragFunction() {} |
| |
| bool DownloadsDragFunction::RunImpl() { |
| scoped_ptr<downloads::Drag::Params> params( |
| downloads::Drag::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| content::WebContents* web_contents = |
| dispatcher()->delegate()->GetVisibleWebContents(); |
| if (InvalidId(download_item, &error_) || |
| Fault(!web_contents, errors::kInvisibleContext, &error_)) |
| return false; |
| RecordApiFunctions(DOWNLOADS_FUNCTION_DRAG); |
| gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath( |
| download_item->GetTargetFilePath(), IconLoader::NORMAL); |
| gfx::NativeView view = web_contents->GetView()->GetNativeView(); |
| { |
| // Enable nested tasks during DnD, while |DragDownload()| blocks. |
| base::MessageLoop::ScopedNestableTaskAllower allow( |
| base::MessageLoop::current()); |
| DragDownloadItem(download_item, icon, view); |
| } |
| return true; |
| } |
| |
| DownloadsSetShelfEnabledFunction::DownloadsSetShelfEnabledFunction() {} |
| |
| DownloadsSetShelfEnabledFunction::~DownloadsSetShelfEnabledFunction() {} |
| |
| bool DownloadsSetShelfEnabledFunction::RunImpl() { |
| scoped_ptr<downloads::SetShelfEnabled::Params> params( |
| downloads::SetShelfEnabled::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| if (!GetExtension()->HasAPIPermission( |
| extensions::APIPermission::kDownloadsShelf)) { |
| error_ = download_extension_errors::kShelfPermission; |
| return false; |
| } |
| |
| RecordApiFunctions(DOWNLOADS_FUNCTION_SET_SHELF_ENABLED); |
| DownloadManager* manager = NULL; |
| DownloadManager* incognito_manager = NULL; |
| GetManagers(GetProfile(), include_incognito(), &manager, &incognito_manager); |
| DownloadService* service = NULL; |
| DownloadService* incognito_service = NULL; |
| if (manager) { |
| service = DownloadServiceFactory::GetForBrowserContext( |
| manager->GetBrowserContext()); |
| service->GetExtensionEventRouter()->SetShelfEnabled( |
| GetExtension(), params->enabled); |
| } |
| if (incognito_manager) { |
| incognito_service = DownloadServiceFactory::GetForBrowserContext( |
| incognito_manager->GetBrowserContext()); |
| incognito_service->GetExtensionEventRouter()->SetShelfEnabled( |
| GetExtension(), params->enabled); |
| } |
| |
| BrowserList* browsers = BrowserList::GetInstance(chrome::GetActiveDesktop()); |
| if (browsers) { |
| for (BrowserList::const_iterator iter = browsers->begin(); |
| iter != browsers->end(); ++iter) { |
| const Browser* browser = *iter; |
| DownloadService* current_service = |
| DownloadServiceFactory::GetForBrowserContext(browser->profile()); |
| if (((current_service == service) || |
| (current_service == incognito_service)) && |
| browser->window()->IsDownloadShelfVisible() && |
| !current_service->IsShelfEnabled()) |
| browser->window()->GetDownloadShelf()->Close(DownloadShelf::AUTOMATIC); |
| } |
| } |
| |
| if (params->enabled && |
| ((manager && !service->IsShelfEnabled()) || |
| (incognito_manager && !incognito_service->IsShelfEnabled()))) { |
| error_ = download_extension_errors::kShelfDisabled; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| DownloadsGetFileIconFunction::DownloadsGetFileIconFunction() |
| : icon_extractor_(new DownloadFileIconExtractorImpl()) { |
| } |
| |
| DownloadsGetFileIconFunction::~DownloadsGetFileIconFunction() {} |
| |
| void DownloadsGetFileIconFunction::SetIconExtractorForTesting( |
| DownloadFileIconExtractor* extractor) { |
| DCHECK(extractor); |
| icon_extractor_.reset(extractor); |
| } |
| |
| bool DownloadsGetFileIconFunction::RunImpl() { |
| scoped_ptr<downloads::GetFileIcon::Params> params( |
| downloads::GetFileIcon::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| const downloads::GetFileIconOptions* options = |
| params->options.get(); |
| int icon_size = kDefaultIconSize; |
| if (options && options->size.get()) |
| icon_size = *options->size.get(); |
| DownloadItem* download_item = |
| GetDownload(GetProfile(), include_incognito(), params->download_id); |
| if (InvalidId(download_item, &error_) || |
| Fault(download_item->GetTargetFilePath().empty(), |
| errors::kEmptyFile, &error_)) |
| return false; |
| // In-progress downloads return the intermediate filename for GetFullPath() |
| // 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( |
| download_item->GetTargetFilePath(), |
| IconLoaderSizeFromPixelSize(icon_size), |
| base::Bind(&DownloadsGetFileIconFunction::OnIconURLExtracted, this))); |
| return true; |
| } |
| |
| void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (Fault(url.empty(), errors::kIconNotFound, &error_)) { |
| SendResponse(false); |
| return; |
| } |
| RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON); |
| SetResult(new base::StringValue(url)); |
| SendResponse(true); |
| } |
| |
| ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter( |
| Profile* profile, |
| DownloadManager* manager) |
| : profile_(profile), |
| notifier_(manager, this) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(profile_); |
| registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, |
| content::Source<Profile>(profile_)); |
| extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| event_router(); |
| if (router) |
| router->RegisterObserver(this, |
| downloads::OnDeterminingFilename::kEventName); |
| } |
| |
| ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| event_router(); |
| if (router) |
| router->UnregisterObserver(this); |
| } |
| |
| void ExtensionDownloadsEventRouter::SetShelfEnabled( |
| const extensions::Extension* extension, bool enabled) { |
| std::set<const extensions::Extension*>::iterator iter = |
| shelf_disabling_extensions_.find(extension); |
| if (iter == shelf_disabling_extensions_.end()) { |
| if (!enabled) |
| shelf_disabling_extensions_.insert(extension); |
| } else if (enabled) { |
| shelf_disabling_extensions_.erase(extension); |
| } |
| } |
| |
| bool ExtensionDownloadsEventRouter::IsShelfEnabled() const { |
| return shelf_disabling_extensions_.empty(); |
| } |
| |
| // 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); |
| if (!data) { |
| no_change.Run(); |
| return; |
| } |
| data->ClearPendingDeterminers(); |
| data->set_filename_change_callbacks(no_change, change); |
| bool any_determiners = false; |
| base::DictionaryValue* json = DownloadItemToJSON( |
| item, profile_).release(); |
| json->SetString(kFilenameKey, suggested_path.LossyDisplayName()); |
| DispatchEvent(downloads::OnDeterminingFilename::kEventName, |
| false, |
| base::Bind(&OnDeterminingFilenameWillDispatchCallback, |
| &any_determiners, |
| data), |
| json); |
| if (!any_determiners) { |
| data->ClearPendingDeterminers(); |
| if (!data->creator_suggested_filename().empty()) { |
| change.Run(data->creator_suggested_filename(), |
| ConvertConflictAction(data->creator_conflict_action())); |
| // If all listeners are removed, don't keep |data| around. |
| data->ResetCreatorSuggestion(); |
| } else { |
| no_change.Run(); |
| } |
| } |
| } |
| |
| void ExtensionDownloadsEventRouter::DetermineFilenameInternal( |
| const base::FilePath& filename, |
| downloads::FilenameConflictAction conflict_action, |
| const std::string& suggesting_extension_id, |
| const base::Time& suggesting_install_time, |
| const std::string& incumbent_extension_id, |
| const base::Time& incumbent_install_time, |
| std::string* winner_extension_id, |
| base::FilePath* determined_filename, |
| downloads::FilenameConflictAction* |
| determined_conflict_action, |
| extensions::ExtensionWarningSet* warnings) { |
| DCHECK(!filename.empty()); |
| DCHECK(!suggesting_extension_id.empty()); |
| |
| if (incumbent_extension_id.empty()) { |
| *winner_extension_id = suggesting_extension_id; |
| *determined_filename = filename; |
| *determined_conflict_action = conflict_action; |
| return; |
| } |
| |
| if (suggesting_install_time < incumbent_install_time) { |
| *winner_extension_id = incumbent_extension_id; |
| warnings->insert( |
| extensions::ExtensionWarning::CreateDownloadFilenameConflictWarning( |
| suggesting_extension_id, |
| incumbent_extension_id, |
| filename, |
| *determined_filename)); |
| return; |
| } |
| |
| *winner_extension_id = suggesting_extension_id; |
| warnings->insert( |
| extensions::ExtensionWarning::CreateDownloadFilenameConflictWarning( |
| incumbent_extension_id, |
| suggesting_extension_id, |
| *determined_filename, |
| filename)); |
| *determined_filename = filename; |
| *determined_conflict_action = conflict_action; |
| } |
| |
| bool ExtensionDownloadsEventRouter::DetermineFilename( |
| Profile* profile, |
| bool include_incognito, |
| const std::string& ext_id, |
| int download_id, |
| const base::FilePath& const_filename, |
| downloads::FilenameConflictAction conflict_action, |
| std::string* error) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DownloadItem* item = GetDownload(profile, include_incognito, download_id); |
| ExtensionDownloadsEventRouterData* data = |
| item ? ExtensionDownloadsEventRouterData::Get(item) : NULL; |
| // 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 (InvalidId(item, error) || |
| Fault(item->GetState() != DownloadItem::IN_PROGRESS, |
| errors::kNotInProgress, error) || |
| Fault(!data, errors::kUnexpectedDeterminer, error) || |
| Fault(data->DeterminerAlreadyReported(ext_id), |
| errors::kTooManyListeners, error)) |
| return false; |
| base::FilePath::StringType filename_str(const_filename.value()); |
| // Allow windows-style directory separators on all platforms. |
| std::replace(filename_str.begin(), filename_str.end(), |
| FILE_PATH_LITERAL('\\'), FILE_PATH_LITERAL('/')); |
| base::FilePath filename(filename_str); |
| bool valid_filename = net::IsSafePortableRelativePath(filename); |
| filename = (valid_filename ? filename.NormalizePathSeparators() : |
| base::FilePath()); |
| // If the invalid filename check is moved to before DeterminerCallback(), then |
| // it will block forever waiting for this ext_id to report. |
| if (Fault(!data->DeterminerCallback( |
| profile, ext_id, filename, conflict_action), |
| errors::kUnexpectedDeterminer, error) || |
| Fault((!const_filename.empty() && !valid_filename), |
| errors::kInvalidFilename, error)) |
| return false; |
| return true; |
| } |
| |
| void ExtensionDownloadsEventRouter::OnListenerRemoved( |
| const extensions::EventListenerInfo& details) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DownloadManager* manager = notifier_.GetManager(); |
| if (!manager) |
| return; |
| bool determiner_removed = ( |
| details.event_name == downloads::OnDeterminingFilename::kEventName); |
| extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| event_router(); |
| bool any_listeners = |
| router->HasEventListener(downloads::OnChanged::kEventName) || |
| router->HasEventListener(downloads::OnDeterminingFilename::kEventName); |
| if (!determiner_removed && any_listeners) |
| return; |
| 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) |
| continue; |
| if (determiner_removed) { |
| // Notify any items that may be waiting for callbacks from this |
| // extension/determiner. 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. |
| data->DeterminerRemoved(details.extension_id); |
| } |
| if (!any_listeners && |
| data->creator_suggested_filename().empty()) { |
| ExtensionDownloadsEventRouterData::Remove(*iter); |
| } |
| } |
| } |
| |
| // 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)); |
| if (download_item->IsTemporary()) |
| return; |
| |
| extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| event_router(); |
| // Avoid allocating a bunch of memory in DownloadItemToJSON if it isn't going |
| // to be used. |
| if (!router || |
| (!router->HasEventListener(downloads::OnCreated::kEventName) && |
| !router->HasEventListener(downloads::OnChanged::kEventName) && |
| !router->HasEventListener( |
| downloads::OnDeterminingFilename::kEventName))) { |
| return; |
| } |
| scoped_ptr<base::DictionaryValue> json_item( |
| DownloadItemToJSON(download_item, profile_)); |
| DispatchEvent(downloads::OnCreated::kEventName, |
| true, |
| extensions::Event::WillDispatchCallback(), |
| json_item->DeepCopy()); |
| if (!ExtensionDownloadsEventRouterData::Get(download_item) && |
| (router->HasEventListener(downloads::OnChanged::kEventName) || |
| router->HasEventListener( |
| downloads::OnDeterminingFilename::kEventName))) { |
| new ExtensionDownloadsEventRouterData(download_item, json_item.Pass()); |
| } |
| } |
| |
| void ExtensionDownloadsEventRouter::OnDownloadUpdated( |
| DownloadManager* manager, DownloadItem* download_item) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)-> |
| event_router(); |
| ExtensionDownloadsEventRouterData* data = |
| ExtensionDownloadsEventRouterData::Get(download_item); |
| if (download_item->IsTemporary() || |
| !router->HasEventListener(downloads::OnChanged::kEventName)) { |
| return; |
| } |
| if (!data) { |
| // The download_item probably transitioned from temporary to not temporary, |
| // or else an event listener was added. |
| data = new ExtensionDownloadsEventRouterData( |
| download_item, |
| scoped_ptr<base::DictionaryValue>(new base::DictionaryValue())); |
| } |
| scoped_ptr<base::DictionaryValue> new_json(DownloadItemToJSON( |
| download_item, profile_)); |
| scoped_ptr<base::DictionaryValue> delta(new base::DictionaryValue()); |
| delta->SetInteger(kIdKey, download_item->GetId()); |
| std::set<std::string> new_fields; |
| bool changed = false; |
| |
| // For each field in the new json representation of the download_item except |
| // the bytesReceived field, if the field has changed from the previous old |
| // json, set the differences in the |delta| object and remember that something |
| // significant changed. |
| for (base::DictionaryValue::Iterator iter(*new_json.get()); |
| !iter.IsAtEnd(); iter.Advance()) { |
| new_fields.insert(iter.key()); |
| if (IsDownloadDeltaField(iter.key())) { |
| const base::Value* old_value = NULL; |
| if (!data->json().HasKey(iter.key()) || |
| (data->json().Get(iter.key(), &old_value) && |
| !iter.value().Equals(old_value))) { |
| delta->Set(iter.key() + ".current", iter.value().DeepCopy()); |
| if (old_value) |
| delta->Set(iter.key() + ".previous", old_value->DeepCopy()); |
| changed = true; |
| } |
| } |
| } |
| |
| // If a field was in the previous json but is not in the new json, set the |
| // difference in |delta|. |
| for (base::DictionaryValue::Iterator iter(data->json()); |
| !iter.IsAtEnd(); iter.Advance()) { |
| if ((new_fields.find(iter.key()) == new_fields.end()) && |
| IsDownloadDeltaField(iter.key())) { |
| // estimatedEndTime disappears after completion, but bytesReceived stays. |
| delta->Set(iter.key() + ".previous", iter.value().DeepCopy()); |
| changed = true; |
| } |
| } |
| |
| // Update the OnChangedStat and dispatch the event if something significant |
| // changed. Replace the stored json with the new json. |
| data->OnItemUpdated(); |
| if (changed) { |
| DispatchEvent(downloads::OnChanged::kEventName, |
| true, |
| extensions::Event::WillDispatchCallback(), |
| delta.release()); |
| data->OnChangedFired(); |
| } |
| data->set_json(new_json.Pass()); |
| } |
| |
| void ExtensionDownloadsEventRouter::OnDownloadRemoved( |
| DownloadManager* manager, DownloadItem* download_item) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| if (download_item->IsTemporary()) |
| return; |
| DispatchEvent(downloads::OnErased::kEventName, |
| true, |
| extensions::Event::WillDispatchCallback(), |
| new base::FundamentalValue( |
| static_cast<int>(download_item->GetId()))); |
| } |
| |
| void ExtensionDownloadsEventRouter::DispatchEvent( |
| const std::string& 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); |
| 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( |
| ¬ification_source); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT, |
| content_source, |
| content::Details<std::string>(&json_args)); |
| } |
| |
| void ExtensionDownloadsEventRouter::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| switch (type) { |
| case chrome::NOTIFICATION_EXTENSION_UNLOADED: { |
| extensions::UnloadedExtensionInfo* unloaded = |
| content::Details<extensions::UnloadedExtensionInfo>(details).ptr(); |
| std::set<const extensions::Extension*>::iterator iter = |
| shelf_disabling_extensions_.find(unloaded->extension); |
| if (iter != shelf_disabling_extensions_.end()) |
| shelf_disabling_extensions_.erase(iter); |
| break; |
| } |
| } |
| } |