blob: 1bfc27b3cd92a9abfcb088e103b4243ddb363cfb [file] [log] [blame]
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/api/downloads/downloads_api.h"
6
7#include <algorithm>
8#include <cctype>
9#include <iterator>
10#include <set>
11#include <string>
12
13#include "base/basictypes.h"
14#include "base/bind.h"
15#include "base/bind_helpers.h"
16#include "base/callback.h"
Ben Murdoch558790d2013-07-30 15:19:42 +010017#include "base/file_util.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000018#include "base/files/file_path.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000019#include "base/json/json_writer.h"
20#include "base/lazy_instance.h"
21#include "base/logging.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000022#include "base/memory/weak_ptr.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000023#include "base/metrics/histogram.h"
24#include "base/stl_util.h"
Torne (Richard Coles)5e3f23d2013-06-11 16:24:11 +010025#include "base/strings/string16.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000026#include "base/strings/string_split.h"
Torne (Richard Coles)5e3f23d2013-06-11 16:24:11 +010027#include "base/strings/string_util.h"
28#include "base/strings/stringprintf.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000029#include "base/values.h"
30#include "chrome/browser/browser_process.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010031#include "chrome/browser/chrome_notification_types.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000032#include "chrome/browser/download/download_danger_prompt.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000033#include "chrome/browser/download/download_file_icon_extractor.h"
Ben Murdoch558790d2013-07-30 15:19:42 +010034#include "chrome/browser/download/download_prefs.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000035#include "chrome/browser/download/download_query.h"
36#include "chrome/browser/download/download_service.h"
37#include "chrome/browser/download/download_service_factory.h"
Ben Murdoch558790d2013-07-30 15:19:42 +010038#include "chrome/browser/download/download_shelf.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000039#include "chrome/browser/download/download_util.h"
40#include "chrome/browser/extensions/event_names.h"
41#include "chrome/browser/extensions/event_router.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000042#include "chrome/browser/extensions/extension_function_dispatcher.h"
43#include "chrome/browser/extensions/extension_info_map.h"
44#include "chrome/browser/extensions/extension_prefs.h"
45#include "chrome/browser/extensions/extension_service.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000046#include "chrome/browser/extensions/extension_system.h"
47#include "chrome/browser/icon_loader.h"
48#include "chrome/browser/icon_manager.h"
Ben Murdoch558790d2013-07-30 15:19:42 +010049#include "chrome/browser/platform_util.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010050#include "chrome/browser/profiles/profile.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000051#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
52#include "chrome/browser/ui/browser.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010053#include "chrome/browser/ui/browser_list.h"
Ben Murdoch558790d2013-07-30 15:19:42 +010054#include "chrome/browser/ui/browser_window.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000055#include "chrome/common/cancelable_task_tracker.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000056#include "chrome/common/extensions/api/downloads.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010057#include "chrome/common/extensions/extension.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010058#include "chrome/common/extensions/permissions/permissions_data.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000059#include "content/public/browser/download_interrupt_reasons.h"
60#include "content/public/browser/download_item.h"
61#include "content/public/browser/download_save_info.h"
62#include "content/public/browser/download_url_parameters.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010063#include "content/public/browser/notification_details.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000064#include "content/public/browser/notification_service.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010065#include "content/public/browser/notification_source.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000066#include "content/public/browser/render_process_host.h"
67#include "content/public/browser/render_view_host.h"
68#include "content/public/browser/resource_context.h"
69#include "content/public/browser/resource_dispatcher_host.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000070#include "content/public/browser/web_contents.h"
71#include "content/public/browser/web_contents_view.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000072#include "net/base/load_flags.h"
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +010073#include "net/base/net_util.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000074#include "net/http/http_util.h"
75#include "net/url_request/url_request.h"
76#include "third_party/skia/include/core/SkBitmap.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000077#include "ui/webui/web_ui_util.h"
Torne (Richard Coles)58218062012-11-14 11:43:16 +000078
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010079namespace events = extensions::event_names;
80
Torne (Richard Coles)58218062012-11-14 11:43:16 +000081using content::BrowserContext;
82using content::BrowserThread;
Torne (Richard Coles)58218062012-11-14 11:43:16 +000083using content::DownloadItem;
84using content::DownloadManager;
85
86namespace download_extension_errors {
87
Ben Murdoch558790d2013-07-30 15:19:42 +010088const char kEmptyFile[] = "Filename not yet determined";
89const char kFileAlreadyDeleted[] = "Download file already deleted";
90const char kIconNotFound[] = "Icon not found";
91const char kInvalidDangerType[] = "Invalid danger type";
92const char kInvalidFilename[] = "Invalid filename";
93const char kInvalidFilter[] = "Invalid query filter";
94const char kInvalidHeader[] = "Invalid request header";
95const char kInvalidId[] = "Invalid downloadId";
96const char kInvalidOrderBy[] = "Invalid orderBy field";
Torne (Richard Coles)58218062012-11-14 11:43:16 +000097const char kInvalidQueryLimit[] = "Invalid query limit";
Ben Murdoch558790d2013-07-30 15:19:42 +010098const char kInvalidState[] = "Invalid state";
99const char kInvalidURL[] = "Invalid URL";
100const char kInvisibleContext[] = "Javascript execution context is not visible "
101 "(tab, window, popup bubble)";
102const char kNotComplete[] = "Download must be complete";
103const char kNotDangerous[] = "Download must be dangerous";
104const char kNotInProgress[] = "Download must be in progress";
105const char kNotResumable[] = "DownloadItem.canResume must be true";
106const char kOpenPermission[] = "The \"downloads.open\" permission is required";
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100107const char kShelfDisabled[] = "Another extension has disabled the shelf";
108const char kShelfPermission[] = "downloads.setShelfEnabled requires the "
109 "\"downloads.shelf\" permission";
Ben Murdoch558790d2013-07-30 15:19:42 +0100110const char kTooManyListeners[] = "Each extension may have at most one "
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000111 "onDeterminingFilename listener between all of its renderer execution "
112 "contexts.";
Ben Murdoch558790d2013-07-30 15:19:42 +0100113const char kUnexpectedDeterminer[] = "Unexpected determineFilename call";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000114
115} // namespace download_extension_errors
116
Ben Murdoch558790d2013-07-30 15:19:42 +0100117namespace errors = download_extension_errors;
118
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000119namespace {
120
121// Default icon size for getFileIcon() in pixels.
122const int kDefaultIconSize = 32;
123
124// Parameter keys
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100125const char kByExtensionIdKey[] = "byExtensionId";
126const char kByExtensionNameKey[] = "byExtensionName";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000127const char kBytesReceivedKey[] = "bytesReceived";
Ben Murdoch558790d2013-07-30 15:19:42 +0100128const char kCanResumeKey[] = "canResume";
129const char kDangerAccepted[] = "accepted";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000130const char kDangerContent[] = "content";
131const char kDangerFile[] = "file";
Ben Murdoch558790d2013-07-30 15:19:42 +0100132const char kDangerHost[] = "host";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000133const char kDangerKey[] = "danger";
134const char kDangerSafe[] = "safe";
135const char kDangerUncommon[] = "uncommon";
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100136const char kDangerUnwanted[] = "unwanted";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000137const char kDangerUrl[] = "url";
138const char kEndTimeKey[] = "endTime";
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000139const char kEndedAfterKey[] = "endedAfter";
140const char kEndedBeforeKey[] = "endedBefore";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000141const char kErrorKey[] = "error";
Ben Murdoch558790d2013-07-30 15:19:42 +0100142const char kEstimatedEndTimeKey[] = "estimatedEndTime";
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000143const char kExistsKey[] = "exists";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000144const char kFileSizeKey[] = "fileSize";
145const char kFilenameKey[] = "filename";
146const char kFilenameRegexKey[] = "filenameRegex";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000147const char kIdKey[] = "id";
Ben Murdoch558790d2013-07-30 15:19:42 +0100148const char kIncognitoKey[] = "incognito";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000149const char kMimeKey[] = "mime";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000150const char kPausedKey[] = "paused";
151const char kQueryKey[] = "query";
Ben Murdoch558790d2013-07-30 15:19:42 +0100152const char kReferrerUrlKey[] = "referrer";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000153const char kStartTimeKey[] = "startTime";
154const char kStartedAfterKey[] = "startedAfter";
155const char kStartedBeforeKey[] = "startedBefore";
156const char kStateComplete[] = "complete";
157const char kStateInProgress[] = "in_progress";
158const char kStateInterrupted[] = "interrupted";
159const char kStateKey[] = "state";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000160const char kTotalBytesGreaterKey[] = "totalBytesGreater";
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000161const char kTotalBytesKey[] = "totalBytes";
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000162const char kTotalBytesLessKey[] = "totalBytesLess";
163const char kUrlKey[] = "url";
164const char kUrlRegexKey[] = "urlRegex";
165
166// Note: Any change to the danger type strings, should be accompanied by a
167// corresponding change to downloads.json.
168const char* kDangerStrings[] = {
169 kDangerSafe,
170 kDangerFile,
171 kDangerUrl,
172 kDangerContent,
173 kDangerSafe,
174 kDangerUncommon,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000175 kDangerAccepted,
176 kDangerHost,
Ben Murdocha3f7b4e2013-07-24 10:36:34 +0100177 kDangerUnwanted
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000178};
179COMPILE_ASSERT(arraysize(kDangerStrings) == content::DOWNLOAD_DANGER_TYPE_MAX,
180 download_danger_type_enum_changed);
181
182// Note: Any change to the state strings, should be accompanied by a
183// corresponding change to downloads.json.
184const char* kStateStrings[] = {
185 kStateInProgress,
186 kStateComplete,
187 kStateInterrupted,
188 kStateInterrupted,
189};
190COMPILE_ASSERT(arraysize(kStateStrings) == DownloadItem::MAX_DOWNLOAD_STATE,
191 download_item_state_enum_changed);
192
193const char* DangerString(content::DownloadDangerType danger) {
194 DCHECK(danger >= 0);
195 DCHECK(danger < static_cast<content::DownloadDangerType>(
196 arraysize(kDangerStrings)));
197 if (danger < 0 || danger >= static_cast<content::DownloadDangerType>(
198 arraysize(kDangerStrings)))
199 return "";
200 return kDangerStrings[danger];
201}
202
203content::DownloadDangerType DangerEnumFromString(const std::string& danger) {
204 for (size_t i = 0; i < arraysize(kDangerStrings); ++i) {
205 if (danger == kDangerStrings[i])
206 return static_cast<content::DownloadDangerType>(i);
207 }
208 return content::DOWNLOAD_DANGER_TYPE_MAX;
209}
210
211const char* StateString(DownloadItem::DownloadState state) {
212 DCHECK(state >= 0);
213 DCHECK(state < static_cast<DownloadItem::DownloadState>(
214 arraysize(kStateStrings)));
215 if (state < 0 || state >= static_cast<DownloadItem::DownloadState>(
216 arraysize(kStateStrings)))
217 return "";
218 return kStateStrings[state];
219}
220
221DownloadItem::DownloadState StateEnumFromString(const std::string& state) {
222 for (size_t i = 0; i < arraysize(kStateStrings); ++i) {
223 if ((kStateStrings[i] != NULL) && (state == kStateStrings[i]))
224 return static_cast<DownloadItem::DownloadState>(i);
225 }
226 return DownloadItem::MAX_DOWNLOAD_STATE;
227}
228
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000229std::string TimeToISO8601(const base::Time& t) {
230 base::Time::Exploded exploded;
231 t.UTCExplode(&exploded);
232 return base::StringPrintf(
233 "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", exploded.year, exploded.month,
234 exploded.day_of_month, exploded.hour, exploded.minute, exploded.second,
235 exploded.millisecond);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000236}
237
238scoped_ptr<base::DictionaryValue> DownloadItemToJSON(
239 DownloadItem* download_item,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100240 Profile* profile) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000241 base::DictionaryValue* json = new base::DictionaryValue();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000242 json->SetBoolean(kExistsKey, !download_item->GetFileExternallyRemoved());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000243 json->SetInteger(kIdKey, download_item->GetId());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000244 const GURL& url = download_item->GetOriginalUrl();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100245 json->SetString(kUrlKey, (url.is_valid() ? url.spec() : std::string()));
Ben Murdoch558790d2013-07-30 15:19:42 +0100246 const GURL& referrer = download_item->GetReferrerUrl();
247 json->SetString(kReferrerUrlKey, (referrer.is_valid() ? referrer.spec()
248 : std::string()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100249 json->SetString(kFilenameKey,
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100250 download_item->GetTargetFilePath().LossyDisplayName());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000251 json->SetString(kDangerKey, DangerString(download_item->GetDangerType()));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000252 json->SetString(kStateKey, StateString(download_item->GetState()));
Ben Murdoch558790d2013-07-30 15:19:42 +0100253 json->SetBoolean(kCanResumeKey, download_item->CanResume());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000254 json->SetBoolean(kPausedKey, download_item->IsPaused());
255 json->SetString(kMimeKey, download_item->GetMimeType());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000256 json->SetString(kStartTimeKey, TimeToISO8601(download_item->GetStartTime()));
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000257 json->SetInteger(kBytesReceivedKey, download_item->GetReceivedBytes());
258 json->SetInteger(kTotalBytesKey, download_item->GetTotalBytes());
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100259 json->SetBoolean(kIncognitoKey, profile->IsOffTheRecord());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000260 if (download_item->GetState() == DownloadItem::INTERRUPTED) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100261 json->SetString(kErrorKey, content::InterruptReasonDebugString(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000262 download_item->GetLastReason()));
263 } else if (download_item->GetState() == DownloadItem::CANCELLED) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100264 json->SetString(kErrorKey, content::InterruptReasonDebugString(
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000265 content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED));
266 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000267 if (!download_item->GetEndTime().is_null())
268 json->SetString(kEndTimeKey, TimeToISO8601(download_item->GetEndTime()));
Ben Murdoch558790d2013-07-30 15:19:42 +0100269 base::TimeDelta time_remaining;
270 if (download_item->TimeRemaining(&time_remaining)) {
271 base::Time now = base::Time::Now();
272 json->SetString(kEstimatedEndTimeKey, TimeToISO8601(now + time_remaining));
273 }
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100274 DownloadedByExtension* by_ext = DownloadedByExtension::Get(download_item);
275 if (by_ext) {
276 json->SetString(kByExtensionIdKey, by_ext->id());
277 json->SetString(kByExtensionNameKey, by_ext->name());
278 // Lookup the extension's current name() in case the user changed their
279 // language. This won't work if the extension was uninstalled, so the name
280 // might be the wrong language.
281 bool include_disabled = true;
282 const extensions::Extension* extension = extensions::ExtensionSystem::Get(
283 profile)->extension_service()->GetExtensionById(
284 by_ext->id(), include_disabled);
285 if (extension)
286 json->SetString(kByExtensionNameKey, extension->name());
287 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000288 // TODO(benjhayden): Implement fileSize.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000289 json->SetInteger(kFileSizeKey, download_item->GetTotalBytes());
290 return scoped_ptr<base::DictionaryValue>(json);
291}
292
293class DownloadFileIconExtractorImpl : public DownloadFileIconExtractor {
294 public:
295 DownloadFileIconExtractorImpl() {}
296
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000297 virtual ~DownloadFileIconExtractorImpl() {}
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000298
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000299 virtual bool ExtractIconURLForPath(const base::FilePath& path,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000300 IconLoader::IconSize icon_size,
301 IconURLCallback callback) OVERRIDE;
302 private:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000303 void OnIconLoadComplete(gfx::Image* icon);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000304
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000305 CancelableTaskTracker cancelable_task_tracker_;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000306 IconURLCallback callback_;
307};
308
309bool DownloadFileIconExtractorImpl::ExtractIconURLForPath(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000310 const base::FilePath& path,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000311 IconLoader::IconSize icon_size,
312 IconURLCallback callback) {
313 callback_ = callback;
314 IconManager* im = g_browser_process->icon_manager();
315 // The contents of the file at |path| may have changed since a previous
316 // request, in which case the associated icon may also have changed.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000317 // Therefore, always call LoadIcon instead of attempting a LookupIcon.
318 im->LoadIcon(path,
319 icon_size,
320 base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete,
321 base::Unretained(this)),
322 &cancelable_task_tracker_);
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000323 return true;
324}
325
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000326void DownloadFileIconExtractorImpl::OnIconLoadComplete(gfx::Image* icon) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000327 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
328 std::string url;
329 if (icon)
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000330 url = webui::GetBitmapDataUrl(icon->AsBitmap());
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000331 callback_.Run(url);
332}
333
334IconLoader::IconSize IconLoaderSizeFromPixelSize(int pixel_size) {
335 switch (pixel_size) {
336 case 16: return IconLoader::SMALL;
337 case 32: return IconLoader::NORMAL;
338 default:
339 NOTREACHED();
340 return IconLoader::NORMAL;
341 }
342}
343
344typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap;
345
346void InitFilterTypeMap(FilterTypeMap& filter_types) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000347 filter_types[kBytesReceivedKey] = DownloadQuery::FILTER_BYTES_RECEIVED;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000348 filter_types[kExistsKey] = DownloadQuery::FILTER_EXISTS;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000349 filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000350 filter_types[kFilenameRegexKey] = DownloadQuery::FILTER_FILENAME_REGEX;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000351 filter_types[kMimeKey] = DownloadQuery::FILTER_MIME;
352 filter_types[kPausedKey] = DownloadQuery::FILTER_PAUSED;
353 filter_types[kQueryKey] = DownloadQuery::FILTER_QUERY;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000354 filter_types[kEndedAfterKey] = DownloadQuery::FILTER_ENDED_AFTER;
355 filter_types[kEndedBeforeKey] = DownloadQuery::FILTER_ENDED_BEFORE;
356 filter_types[kEndTimeKey] = DownloadQuery::FILTER_END_TIME;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000357 filter_types[kStartedAfterKey] = DownloadQuery::FILTER_STARTED_AFTER;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000358 filter_types[kStartedBeforeKey] = DownloadQuery::FILTER_STARTED_BEFORE;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000359 filter_types[kStartTimeKey] = DownloadQuery::FILTER_START_TIME;
360 filter_types[kTotalBytesKey] = DownloadQuery::FILTER_TOTAL_BYTES;
361 filter_types[kTotalBytesGreaterKey] =
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000362 DownloadQuery::FILTER_TOTAL_BYTES_GREATER;
363 filter_types[kTotalBytesLessKey] = DownloadQuery::FILTER_TOTAL_BYTES_LESS;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000364 filter_types[kUrlKey] = DownloadQuery::FILTER_URL;
365 filter_types[kUrlRegexKey] = DownloadQuery::FILTER_URL_REGEX;
366}
367
368typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap;
369
370void InitSortTypeMap(SortTypeMap& sorter_types) {
371 sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED;
372 sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000373 sorter_types[kEndTimeKey] = DownloadQuery::SORT_END_TIME;
374 sorter_types[kExistsKey] = DownloadQuery::SORT_EXISTS;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000375 sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME;
376 sorter_types[kMimeKey] = DownloadQuery::SORT_MIME;
377 sorter_types[kPausedKey] = DownloadQuery::SORT_PAUSED;
378 sorter_types[kStartTimeKey] = DownloadQuery::SORT_START_TIME;
379 sorter_types[kStateKey] = DownloadQuery::SORT_STATE;
380 sorter_types[kTotalBytesKey] = DownloadQuery::SORT_TOTAL_BYTES;
381 sorter_types[kUrlKey] = DownloadQuery::SORT_URL;
382}
383
384bool IsNotTemporaryDownloadFilter(const DownloadItem& download_item) {
385 return !download_item.IsTemporary();
386}
387
388// Set |manager| to the on-record DownloadManager, and |incognito_manager| to
389// the off-record DownloadManager if one exists and is requested via
390// |include_incognito|. This should work regardless of whether |profile| is
391// original or incognito.
392void GetManagers(
393 Profile* profile,
394 bool include_incognito,
395 DownloadManager** manager,
396 DownloadManager** incognito_manager) {
397 *manager = BrowserContext::GetDownloadManager(profile->GetOriginalProfile());
398 if (profile->HasOffTheRecordProfile() &&
399 (include_incognito ||
400 profile->IsOffTheRecord())) {
401 *incognito_manager = BrowserContext::GetDownloadManager(
402 profile->GetOffTheRecordProfile());
403 } else {
404 *incognito_manager = NULL;
405 }
406}
407
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000408DownloadItem* GetDownload(Profile* profile, bool include_incognito, int id) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000409 DownloadManager* manager = NULL;
410 DownloadManager* incognito_manager = NULL;
411 GetManagers(profile, include_incognito, &manager, &incognito_manager);
412 DownloadItem* download_item = manager->GetDownload(id);
413 if (!download_item && incognito_manager)
414 download_item = incognito_manager->GetDownload(id);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000415 return download_item;
416}
417
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000418enum DownloadsFunctionName {
419 DOWNLOADS_FUNCTION_DOWNLOAD = 0,
420 DOWNLOADS_FUNCTION_SEARCH = 1,
421 DOWNLOADS_FUNCTION_PAUSE = 2,
422 DOWNLOADS_FUNCTION_RESUME = 3,
423 DOWNLOADS_FUNCTION_CANCEL = 4,
424 DOWNLOADS_FUNCTION_ERASE = 5,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100425 // 6 unused
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000426 DOWNLOADS_FUNCTION_ACCEPT_DANGER = 7,
427 DOWNLOADS_FUNCTION_SHOW = 8,
428 DOWNLOADS_FUNCTION_DRAG = 9,
429 DOWNLOADS_FUNCTION_GET_FILE_ICON = 10,
430 DOWNLOADS_FUNCTION_OPEN = 11,
Ben Murdoch558790d2013-07-30 15:19:42 +0100431 DOWNLOADS_FUNCTION_REMOVE_FILE = 12,
432 DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER = 13,
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100433 DOWNLOADS_FUNCTION_SET_SHELF_ENABLED = 14,
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000434 // Insert new values here, not at the beginning.
435 DOWNLOADS_FUNCTION_LAST
436};
437
438void RecordApiFunctions(DownloadsFunctionName function) {
439 UMA_HISTOGRAM_ENUMERATION("Download.ApiFunctions",
440 function,
441 DOWNLOADS_FUNCTION_LAST);
442}
443
444void CompileDownloadQueryOrderBy(
Ben Murdoch558790d2013-07-30 15:19:42 +0100445 const std::vector<std::string>& order_by_strs,
446 std::string* error,
447 DownloadQuery* query) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000448 // TODO(benjhayden): Consider switching from LazyInstance to explicit string
449 // comparisons.
450 static base::LazyInstance<SortTypeMap> sorter_types =
451 LAZY_INSTANCE_INITIALIZER;
452 if (sorter_types.Get().size() == 0)
453 InitSortTypeMap(sorter_types.Get());
454
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000455 for (std::vector<std::string>::const_iterator iter = order_by_strs.begin();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000456 iter != order_by_strs.end(); ++iter) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000457 std::string term_str = *iter;
458 if (term_str.empty())
459 continue;
460 DownloadQuery::SortDirection direction = DownloadQuery::ASCENDING;
461 if (term_str[0] == '-') {
462 direction = DownloadQuery::DESCENDING;
463 term_str = term_str.substr(1);
464 }
465 SortTypeMap::const_iterator sorter_type =
466 sorter_types.Get().find(term_str);
467 if (sorter_type == sorter_types.Get().end()) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100468 *error = errors::kInvalidOrderBy;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000469 return;
470 }
471 query->AddSorter(sorter_type->second, direction);
472 }
473}
474
475void RunDownloadQuery(
476 const extensions::api::downloads::DownloadQuery& query_in,
477 DownloadManager* manager,
478 DownloadManager* incognito_manager,
479 std::string* error,
480 DownloadQuery::DownloadVector* results) {
481 // TODO(benjhayden): Consider switching from LazyInstance to explicit string
482 // comparisons.
483 static base::LazyInstance<FilterTypeMap> filter_types =
484 LAZY_INSTANCE_INITIALIZER;
485 if (filter_types.Get().size() == 0)
486 InitFilterTypeMap(filter_types.Get());
487
488 DownloadQuery query_out;
489
Ben Murdoch558790d2013-07-30 15:19:42 +0100490 size_t limit = 1000;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000491 if (query_in.limit.get()) {
492 if (*query_in.limit.get() < 0) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100493 *error = errors::kInvalidQueryLimit;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000494 return;
495 }
Ben Murdoch558790d2013-07-30 15:19:42 +0100496 limit = *query_in.limit.get();
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000497 }
Ben Murdoch558790d2013-07-30 15:19:42 +0100498 if (limit > 0) {
499 query_out.Limit(limit);
500 }
501
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000502 std::string state_string =
503 extensions::api::downloads::ToString(query_in.state);
504 if (!state_string.empty()) {
505 DownloadItem::DownloadState state = StateEnumFromString(state_string);
506 if (state == DownloadItem::MAX_DOWNLOAD_STATE) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100507 *error = errors::kInvalidState;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000508 return;
509 }
510 query_out.AddFilter(state);
511 }
512 std::string danger_string =
513 extensions::api::downloads::ToString(query_in.danger);
514 if (!danger_string.empty()) {
515 content::DownloadDangerType danger_type = DangerEnumFromString(
516 danger_string);
517 if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100518 *error = errors::kInvalidDangerType;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000519 return;
520 }
521 query_out.AddFilter(danger_type);
522 }
523 if (query_in.order_by.get()) {
524 CompileDownloadQueryOrderBy(*query_in.order_by.get(), error, &query_out);
525 if (!error->empty())
526 return;
527 }
528
529 scoped_ptr<base::DictionaryValue> query_in_value(query_in.ToValue().Pass());
530 for (base::DictionaryValue::Iterator query_json_field(*query_in_value.get());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100531 !query_json_field.IsAtEnd(); query_json_field.Advance()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000532 FilterTypeMap::const_iterator filter_type =
533 filter_types.Get().find(query_json_field.key());
534 if (filter_type != filter_types.Get().end()) {
535 if (!query_out.AddFilter(filter_type->second, query_json_field.value())) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100536 *error = errors::kInvalidFilter;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000537 return;
538 }
539 }
540 }
541
542 DownloadQuery::DownloadVector all_items;
543 if (query_in.id.get()) {
544 DownloadItem* download_item = manager->GetDownload(*query_in.id.get());
545 if (!download_item && incognito_manager)
546 download_item = incognito_manager->GetDownload(*query_in.id.get());
547 if (download_item)
548 all_items.push_back(download_item);
549 } else {
550 manager->GetAllDownloads(&all_items);
551 if (incognito_manager)
552 incognito_manager->GetAllDownloads(&all_items);
553 }
554 query_out.AddFilter(base::Bind(&IsNotTemporaryDownloadFilter));
555 query_out.Search(all_items.begin(), all_items.end(), results);
556}
557
Ben Murdoch558790d2013-07-30 15:19:42 +0100558DownloadPathReservationTracker::FilenameConflictAction ConvertConflictAction(
559 extensions::api::downloads::FilenameConflictAction action) {
560 switch (action) {
561 case extensions::api::downloads::FILENAME_CONFLICT_ACTION_NONE:
562 case extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY:
563 return DownloadPathReservationTracker::UNIQUIFY;
564 case extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE:
565 return DownloadPathReservationTracker::OVERWRITE;
566 case extensions::api::downloads::FILENAME_CONFLICT_ACTION_PROMPT:
567 return DownloadPathReservationTracker::PROMPT;
568 }
569 NOTREACHED();
570 return DownloadPathReservationTracker::UNIQUIFY;
571}
572
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000573class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
574 public:
575 static ExtensionDownloadsEventRouterData* Get(DownloadItem* download_item) {
576 base::SupportsUserData::Data* data = download_item->GetUserData(kKey);
577 return (data == NULL) ? NULL :
578 static_cast<ExtensionDownloadsEventRouterData*>(data);
579 }
580
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100581 static void Remove(DownloadItem* download_item) {
582 download_item->RemoveUserData(kKey);
583 }
584
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000585 explicit ExtensionDownloadsEventRouterData(
586 DownloadItem* download_item,
587 scoped_ptr<base::DictionaryValue> json_item)
588 : updated_(0),
589 changed_fired_(0),
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000590 json_(json_item.Pass()),
Ben Murdoch558790d2013-07-30 15:19:42 +0100591 creator_conflict_action_(
592 extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100593 determined_conflict_action_(
594 extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000595 download_item->SetUserData(kKey, this);
596 }
597
598 virtual ~ExtensionDownloadsEventRouterData() {
599 if (updated_ > 0) {
600 UMA_HISTOGRAM_PERCENTAGE("Download.OnChanged",
601 (changed_fired_ * 100 / updated_));
602 }
603 }
604
605 const base::DictionaryValue& json() const { return *json_.get(); }
606 void set_json(scoped_ptr<base::DictionaryValue> json_item) {
607 json_ = json_item.Pass();
608 }
609
610 void OnItemUpdated() { ++updated_; }
611 void OnChangedFired() { ++changed_fired_; }
612
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000613 void set_filename_change_callbacks(
614 const base::Closure& no_change,
615 const ExtensionDownloadsEventRouter::FilenameChangedCallback& change) {
616 filename_no_change_ = no_change;
617 filename_change_ = change;
Ben Murdoch558790d2013-07-30 15:19:42 +0100618 determined_filename_ = creator_suggested_filename_;
619 determined_conflict_action_ = creator_conflict_action_;
620 // determiner_.install_time should default to 0 so that creator suggestions
621 // should be lower priority than any actual onDeterminingFilename listeners.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000622 }
623
624 void ClearPendingDeterminers() {
625 determined_filename_.clear();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100626 determined_conflict_action_ =
627 extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000628 determiner_ = DeterminerInfo();
629 filename_no_change_ = base::Closure();
630 filename_change_ = ExtensionDownloadsEventRouter::FilenameChangedCallback();
631 weak_ptr_factory_.reset();
632 determiners_.clear();
633 }
634
635 void DeterminerRemoved(const std::string& extension_id) {
636 for (DeterminerInfoVector::iterator iter = determiners_.begin();
637 iter != determiners_.end();) {
638 if (iter->extension_id == extension_id) {
639 iter = determiners_.erase(iter);
640 } else {
641 ++iter;
642 }
643 }
644 // If we just removed the last unreported determiner, then we need to call a
645 // callback.
646 CheckAllDeterminersCalled();
647 }
648
649 void AddPendingDeterminer(const std::string& extension_id,
650 const base::Time& installed) {
651 for (size_t index = 0; index < determiners_.size(); ++index) {
652 if (determiners_[index].extension_id == extension_id) {
653 DCHECK(false) << extension_id;
654 return;
655 }
656 }
657 determiners_.push_back(DeterminerInfo(extension_id, installed));
658 }
659
660 bool DeterminerAlreadyReported(const std::string& extension_id) {
661 for (size_t index = 0; index < determiners_.size(); ++index) {
662 if (determiners_[index].extension_id == extension_id) {
663 return determiners_[index].reported;
664 }
665 }
666 return false;
667 }
668
Ben Murdoch558790d2013-07-30 15:19:42 +0100669 void CreatorSuggestedFilename(
670 const base::FilePath& filename,
671 extensions::api::downloads::FilenameConflictAction conflict_action) {
672 creator_suggested_filename_ = filename;
673 creator_conflict_action_ = conflict_action;
674 }
675
676 base::FilePath creator_suggested_filename() const {
677 return creator_suggested_filename_;
678 }
679
680 extensions::api::downloads::FilenameConflictAction
681 creator_conflict_action() const {
682 return creator_conflict_action_;
683 }
684
685 void ResetCreatorSuggestion() {
686 creator_suggested_filename_.clear();
687 creator_conflict_action_ =
688 extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY;
689 }
690
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100691 // Returns false if this |extension_id| was not expected or if this
692 // |extension_id| has already reported. The caller is responsible for
693 // validating |filename|.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000694 bool DeterminerCallback(
695 const std::string& extension_id,
696 const base::FilePath& filename,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100697 extensions::api::downloads::FilenameConflictAction conflict_action) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000698 bool found_info = false;
699 for (size_t index = 0; index < determiners_.size(); ++index) {
700 if (determiners_[index].extension_id == extension_id) {
701 found_info = true;
702 if (determiners_[index].reported)
703 return false;
704 determiners_[index].reported = true;
705 // Do not use filename if another determiner has already overridden the
706 // filename and they take precedence. Extensions that were installed
707 // later take precedence over previous extensions.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100708 if (!filename.empty() &&
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000709 (determiner_.extension_id.empty() ||
710 (determiners_[index].install_time > determiner_.install_time))) {
711 determined_filename_ = filename;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100712 determined_conflict_action_ = conflict_action;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000713 determiner_ = determiners_[index];
714 }
715 break;
716 }
717 }
718 if (!found_info)
719 return false;
720 CheckAllDeterminersCalled();
721 return true;
722 }
723
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000724 private:
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000725 struct DeterminerInfo {
726 DeterminerInfo();
727 DeterminerInfo(const std::string& e_id,
728 const base::Time& installed);
729 ~DeterminerInfo();
730
731 std::string extension_id;
732 base::Time install_time;
733 bool reported;
734 };
735 typedef std::vector<DeterminerInfo> DeterminerInfoVector;
736
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000737 static const char kKey[];
738
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000739 // This is safe to call even while not waiting for determiners to call back;
740 // in that case, the callbacks will be null so they won't be Run.
741 void CheckAllDeterminersCalled() {
742 for (DeterminerInfoVector::iterator iter = determiners_.begin();
743 iter != determiners_.end(); ++iter) {
744 if (!iter->reported)
745 return;
746 }
747 if (determined_filename_.empty()) {
748 if (!filename_no_change_.is_null())
749 filename_no_change_.Run();
750 } else {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100751 if (!filename_change_.is_null()) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100752 filename_change_.Run(determined_filename_, ConvertConflictAction(
753 determined_conflict_action_));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100754 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000755 }
756 // Don't clear determiners_ immediately in case there's a second listener
757 // for one of the extensions, so that DetermineFilename can return
Ben Murdoch558790d2013-07-30 15:19:42 +0100758 // kTooManyListeners. After a few seconds, DetermineFilename will return
759 // kUnexpectedDeterminer instead of kTooManyListeners so that determiners_
760 // doesn't keep hogging memory.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000761 weak_ptr_factory_.reset(
762 new base::WeakPtrFactory<ExtensionDownloadsEventRouterData>(this));
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100763 base::MessageLoopForUI::current()->PostDelayedTask(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000764 FROM_HERE,
765 base::Bind(&ExtensionDownloadsEventRouterData::ClearPendingDeterminers,
766 weak_ptr_factory_->GetWeakPtr()),
767 base::TimeDelta::FromSeconds(30));
768 }
769
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000770 int updated_;
771 int changed_fired_;
772 scoped_ptr<base::DictionaryValue> json_;
773
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000774 base::Closure filename_no_change_;
775 ExtensionDownloadsEventRouter::FilenameChangedCallback filename_change_;
776
777 DeterminerInfoVector determiners_;
778
Ben Murdoch558790d2013-07-30 15:19:42 +0100779 base::FilePath creator_suggested_filename_;
780 extensions::api::downloads::FilenameConflictAction
781 creator_conflict_action_;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000782 base::FilePath determined_filename_;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100783 extensions::api::downloads::FilenameConflictAction
784 determined_conflict_action_;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000785 DeterminerInfo determiner_;
786
787 scoped_ptr<base::WeakPtrFactory<ExtensionDownloadsEventRouterData> >
788 weak_ptr_factory_;
789
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000790 DISALLOW_COPY_AND_ASSIGN(ExtensionDownloadsEventRouterData);
791};
792
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000793ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo(
794 const std::string& e_id,
795 const base::Time& installed)
796 : extension_id(e_id),
797 install_time(installed),
798 reported(false) {
799}
800
801ExtensionDownloadsEventRouterData::DeterminerInfo::DeterminerInfo()
802 : reported(false) {
803}
804
805ExtensionDownloadsEventRouterData::DeterminerInfo::~DeterminerInfo() {}
806
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000807const char ExtensionDownloadsEventRouterData::kKey[] =
808 "DownloadItem ExtensionDownloadsEventRouterData";
809
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000810class ManagerDestructionObserver : public DownloadManager::Observer {
811 public:
812 static void CheckForHistoryFilesRemoval(DownloadManager* manager) {
813 if (!manager)
814 return;
815 if (!manager_file_existence_last_checked_)
816 manager_file_existence_last_checked_ =
817 new std::map<DownloadManager*, ManagerDestructionObserver*>();
818 if (!(*manager_file_existence_last_checked_)[manager])
819 (*manager_file_existence_last_checked_)[manager] =
820 new ManagerDestructionObserver(manager);
821 (*manager_file_existence_last_checked_)[manager]->
822 CheckForHistoryFilesRemovalInternal();
823 }
824
825 private:
826 static const int kFileExistenceRateLimitSeconds = 10;
827
828 explicit ManagerDestructionObserver(DownloadManager* manager)
829 : manager_(manager) {
830 manager_->AddObserver(this);
831 }
832
833 virtual ~ManagerDestructionObserver() {
834 manager_->RemoveObserver(this);
835 }
836
837 virtual void ManagerGoingDown(DownloadManager* manager) OVERRIDE {
838 manager_file_existence_last_checked_->erase(manager);
839 if (manager_file_existence_last_checked_->size() == 0) {
840 delete manager_file_existence_last_checked_;
841 manager_file_existence_last_checked_ = NULL;
842 }
843 }
844
845 void CheckForHistoryFilesRemovalInternal() {
846 base::Time now(base::Time::Now());
847 int delta = now.ToTimeT() - last_checked_.ToTimeT();
848 if (delta > kFileExistenceRateLimitSeconds) {
849 last_checked_ = now;
850 manager_->CheckForHistoryFilesRemoval();
851 }
852 }
853
854 static std::map<DownloadManager*, ManagerDestructionObserver*>*
855 manager_file_existence_last_checked_;
856
857 DownloadManager* manager_;
858 base::Time last_checked_;
859
860 DISALLOW_COPY_AND_ASSIGN(ManagerDestructionObserver);
861};
862
863std::map<DownloadManager*, ManagerDestructionObserver*>*
864 ManagerDestructionObserver::manager_file_existence_last_checked_ = NULL;
865
866void OnDeterminingFilenameWillDispatchCallback(
867 bool* any_determiners,
868 ExtensionDownloadsEventRouterData* data,
869 Profile* profile,
870 const extensions::Extension* extension,
Ben Murdocheb525c52013-07-10 11:40:50 +0100871 base::ListValue* event_args) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000872 *any_determiners = true;
873 base::Time installed = extensions::ExtensionSystem::Get(
874 profile)->extension_service()->extension_prefs()->
875 GetInstallTime(extension->id());
876 data->AddPendingDeterminer(extension->id(), installed);
877}
878
Ben Murdoch558790d2013-07-30 15:19:42 +0100879bool Fault(bool error,
880 const char* message_in,
881 std::string* message_out) {
882 if (!error)
883 return false;
884 *message_out = message_in;
885 return true;
886}
887
888bool InvalidId(DownloadItem* valid_item, std::string* message_out) {
889 return Fault(!valid_item, errors::kInvalidId, message_out);
890}
891
892bool IsDownloadDeltaField(const std::string& field) {
893 return ((field == kUrlKey) ||
894 (field == kFilenameKey) ||
895 (field == kDangerKey) ||
896 (field == kMimeKey) ||
897 (field == kStartTimeKey) ||
898 (field == kEndTimeKey) ||
899 (field == kStateKey) ||
900 (field == kCanResumeKey) ||
901 (field == kPausedKey) ||
902 (field == kErrorKey) ||
903 (field == kTotalBytesKey) ||
904 (field == kFileSizeKey) ||
905 (field == kExistsKey));
906}
907
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000908} // namespace
909
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100910const char DownloadedByExtension::kKey[] =
911 "DownloadItem DownloadedByExtension";
912
913DownloadedByExtension* DownloadedByExtension::Get(
914 content::DownloadItem* item) {
915 base::SupportsUserData::Data* data = item->GetUserData(kKey);
916 return (data == NULL) ? NULL :
917 static_cast<DownloadedByExtension*>(data);
918}
919
920DownloadedByExtension::DownloadedByExtension(
921 content::DownloadItem* item,
922 const std::string& id,
923 const std::string& name)
924 : id_(id),
925 name_(name) {
926 item->SetUserData(kKey, this);
927}
928
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000929DownloadsDownloadFunction::DownloadsDownloadFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000930
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000931DownloadsDownloadFunction::~DownloadsDownloadFunction() {}
932
933bool DownloadsDownloadFunction::RunImpl() {
934 scoped_ptr<extensions::api::downloads::Download::Params> params(
935 extensions::api::downloads::Download::Params::Create(*args_));
936 EXTENSION_FUNCTION_VALIDATE(params.get());
937 const extensions::api::downloads::DownloadOptions& options = params->options;
938 GURL download_url(options.url);
Ben Murdochbb1529c2013-08-08 10:24:53 +0100939 if (Fault(!download_url.is_valid(), errors::kInvalidURL, &error_))
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000940 return false;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000941
942 Profile* current_profile = profile();
943 if (include_incognito() && profile()->HasOffTheRecordProfile())
944 current_profile = profile()->GetOffTheRecordProfile();
945
946 scoped_ptr<content::DownloadUrlParameters> download_params(
947 new content::DownloadUrlParameters(
948 download_url,
949 render_view_host()->GetProcess()->GetID(),
950 render_view_host()->GetRoutingID(),
951 current_profile->GetResourceContext()));
952
Ben Murdoch558790d2013-07-30 15:19:42 +0100953 base::FilePath creator_suggested_filename;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000954 if (options.filename.get()) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100955#if defined(OS_WIN)
956 // Can't get filename16 from options.ToValue() because that converts it from
957 // std::string.
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000958 base::DictionaryValue* options_value = NULL;
959 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options_value));
Ben Murdoch558790d2013-07-30 15:19:42 +0100960 base::string16 filename16;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000961 EXTENSION_FUNCTION_VALIDATE(options_value->GetString(
962 kFilenameKey, &filename16));
Ben Murdoch558790d2013-07-30 15:19:42 +0100963 creator_suggested_filename = base::FilePath(filename16);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000964#elif defined(OS_POSIX)
Ben Murdoch558790d2013-07-30 15:19:42 +0100965 creator_suggested_filename = base::FilePath(*options.filename.get());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000966#endif
Ben Murdoch558790d2013-07-30 15:19:42 +0100967 if (!net::IsSafePortableRelativePath(creator_suggested_filename)) {
968 error_ = errors::kInvalidFilename;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000969 return false;
970 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000971 }
972
973 if (options.save_as.get())
974 download_params->set_prompt(*options.save_as.get());
975
976 if (options.headers.get()) {
977 typedef extensions::api::downloads::HeaderNameValuePair HeaderNameValuePair;
978 for (std::vector<linked_ptr<HeaderNameValuePair> >::const_iterator iter =
979 options.headers->begin();
980 iter != options.headers->end();
981 ++iter) {
982 const HeaderNameValuePair& name_value = **iter;
983 if (!net::HttpUtil::IsSafeHeader(name_value.name)) {
Ben Murdoch558790d2013-07-30 15:19:42 +0100984 error_ = errors::kInvalidHeader;
Torne (Richard Coles)58218062012-11-14 11:43:16 +0000985 return false;
986 }
987 download_params->add_request_header(name_value.name, name_value.value);
988 }
989 }
990
991 std::string method_string =
992 extensions::api::downloads::ToString(options.method);
993 if (!method_string.empty())
994 download_params->set_method(method_string);
995 if (options.body.get())
996 download_params->set_post_body(*options.body.get());
997 download_params->set_callback(base::Bind(
Ben Murdoch558790d2013-07-30 15:19:42 +0100998 &DownloadsDownloadFunction::OnStarted, this,
999 creator_suggested_filename, options.conflict_action));
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001000 // Prevent login prompts for 401/407 responses.
1001 download_params->set_load_flags(net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
1002
1003 DownloadManager* manager = BrowserContext::GetDownloadManager(
1004 current_profile);
1005 manager->DownloadUrl(download_params.Pass());
Ben Murdoch558790d2013-07-30 15:19:42 +01001006 download_util::RecordDownloadSource(download_util::INITIATED_BY_EXTENSION);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001007 RecordApiFunctions(DOWNLOADS_FUNCTION_DOWNLOAD);
1008 return true;
1009}
1010
1011void DownloadsDownloadFunction::OnStarted(
Ben Murdoch558790d2013-07-30 15:19:42 +01001012 const base::FilePath& creator_suggested_filename,
1013 extensions::api::downloads::FilenameConflictAction creator_conflict_action,
1014 DownloadItem* item,
1015 net::Error error) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001016 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1017 VLOG(1) << __FUNCTION__ << " " << item << " " << error;
1018 if (item) {
1019 DCHECK_EQ(net::OK, error);
1020 SetResult(base::Value::CreateIntegerValue(item->GetId()));
Ben Murdoch558790d2013-07-30 15:19:42 +01001021 if (!creator_suggested_filename.empty()) {
1022 ExtensionDownloadsEventRouterData* data =
1023 ExtensionDownloadsEventRouterData::Get(item);
1024 if (!data) {
1025 data = new ExtensionDownloadsEventRouterData(
1026 item,
1027 scoped_ptr<base::DictionaryValue>(new base::DictionaryValue()));
1028 }
1029 data->CreatorSuggestedFilename(
1030 creator_suggested_filename, creator_conflict_action);
1031 }
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001032 new DownloadedByExtension(
1033 item, GetExtension()->id(), GetExtension()->name());
1034 item->UpdateObservers();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001035 } else {
1036 DCHECK_NE(net::OK, error);
1037 error_ = net::ErrorToString(error);
1038 }
1039 SendResponse(error_.empty());
1040}
1041
1042DownloadsSearchFunction::DownloadsSearchFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001043
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001044DownloadsSearchFunction::~DownloadsSearchFunction() {}
1045
1046bool DownloadsSearchFunction::RunImpl() {
1047 scoped_ptr<extensions::api::downloads::Search::Params> params(
1048 extensions::api::downloads::Search::Params::Create(*args_));
1049 EXTENSION_FUNCTION_VALIDATE(params.get());
1050 DownloadManager* manager = NULL;
1051 DownloadManager* incognito_manager = NULL;
1052 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001053 ManagerDestructionObserver::CheckForHistoryFilesRemoval(manager);
1054 ManagerDestructionObserver::CheckForHistoryFilesRemoval(incognito_manager);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001055 DownloadQuery::DownloadVector results;
1056 RunDownloadQuery(params->query,
1057 manager,
1058 incognito_manager,
1059 &error_,
1060 &results);
1061 if (!error_.empty())
1062 return false;
1063
1064 base::ListValue* json_results = new base::ListValue();
1065 for (DownloadManager::DownloadVector::const_iterator it = results.begin();
1066 it != results.end(); ++it) {
1067 DownloadItem* download_item = *it;
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001068 uint32 download_id = download_item->GetId();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001069 bool off_record = ((incognito_manager != NULL) &&
1070 (incognito_manager->GetDownload(download_id) != NULL));
1071 scoped_ptr<base::DictionaryValue> json_item(DownloadItemToJSON(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001072 *it, off_record ? profile()->GetOffTheRecordProfile()
1073 : profile()->GetOriginalProfile()));
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001074 json_results->Append(json_item.release());
1075 }
1076 SetResult(json_results);
1077 RecordApiFunctions(DOWNLOADS_FUNCTION_SEARCH);
1078 return true;
1079}
1080
1081DownloadsPauseFunction::DownloadsPauseFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001082
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001083DownloadsPauseFunction::~DownloadsPauseFunction() {}
1084
1085bool DownloadsPauseFunction::RunImpl() {
1086 scoped_ptr<extensions::api::downloads::Pause::Params> params(
1087 extensions::api::downloads::Pause::Params::Create(*args_));
1088 EXTENSION_FUNCTION_VALIDATE(params.get());
Ben Murdoch558790d2013-07-30 15:19:42 +01001089 DownloadItem* download_item = GetDownload(
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001090 profile(), include_incognito(), params->download_id);
Ben Murdoch558790d2013-07-30 15:19:42 +01001091 if (InvalidId(download_item, &error_) ||
1092 Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
1093 errors::kNotInProgress, &error_))
1094 return false;
1095 // If the item is already paused, this is a no-op and the operation will
1096 // silently succeed.
1097 download_item->Pause();
1098 RecordApiFunctions(DOWNLOADS_FUNCTION_PAUSE);
1099 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001100}
1101
1102DownloadsResumeFunction::DownloadsResumeFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001103
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001104DownloadsResumeFunction::~DownloadsResumeFunction() {}
1105
1106bool DownloadsResumeFunction::RunImpl() {
1107 scoped_ptr<extensions::api::downloads::Resume::Params> params(
1108 extensions::api::downloads::Resume::Params::Create(*args_));
1109 EXTENSION_FUNCTION_VALIDATE(params.get());
Ben Murdoch558790d2013-07-30 15:19:42 +01001110 DownloadItem* download_item = GetDownload(
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001111 profile(), include_incognito(), params->download_id);
Ben Murdoch558790d2013-07-30 15:19:42 +01001112 if (InvalidId(download_item, &error_) ||
1113 Fault(download_item->IsPaused() && !download_item->CanResume(),
1114 errors::kNotResumable, &error_))
1115 return false;
1116 // Note that if the item isn't paused, this will be a no-op, and the extension
1117 // call will seem successful.
1118 download_item->Resume();
1119 RecordApiFunctions(DOWNLOADS_FUNCTION_RESUME);
1120 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001121}
1122
1123DownloadsCancelFunction::DownloadsCancelFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001124
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001125DownloadsCancelFunction::~DownloadsCancelFunction() {}
1126
1127bool DownloadsCancelFunction::RunImpl() {
1128 scoped_ptr<extensions::api::downloads::Resume::Params> params(
1129 extensions::api::downloads::Resume::Params::Create(*args_));
1130 EXTENSION_FUNCTION_VALIDATE(params.get());
Ben Murdoch558790d2013-07-30 15:19:42 +01001131 DownloadItem* download_item = GetDownload(
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001132 profile(), include_incognito(), params->download_id);
Ben Murdoch558790d2013-07-30 15:19:42 +01001133 if (download_item &&
1134 (download_item->GetState() == DownloadItem::IN_PROGRESS))
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001135 download_item->Cancel(true);
1136 // |download_item| can be NULL if the download ID was invalid or if the
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001137 // download is not currently active. Either way, it's not a failure.
1138 RecordApiFunctions(DOWNLOADS_FUNCTION_CANCEL);
1139 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001140}
1141
1142DownloadsEraseFunction::DownloadsEraseFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001143
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001144DownloadsEraseFunction::~DownloadsEraseFunction() {}
1145
1146bool DownloadsEraseFunction::RunImpl() {
1147 scoped_ptr<extensions::api::downloads::Erase::Params> params(
1148 extensions::api::downloads::Erase::Params::Create(*args_));
1149 EXTENSION_FUNCTION_VALIDATE(params.get());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001150 DownloadManager* manager = NULL;
1151 DownloadManager* incognito_manager = NULL;
1152 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
1153 DownloadQuery::DownloadVector results;
1154 RunDownloadQuery(params->query,
1155 manager,
1156 incognito_manager,
1157 &error_,
1158 &results);
1159 if (!error_.empty())
1160 return false;
1161 base::ListValue* json_results = new base::ListValue();
1162 for (DownloadManager::DownloadVector::const_iterator it = results.begin();
1163 it != results.end(); ++it) {
1164 json_results->Append(base::Value::CreateIntegerValue((*it)->GetId()));
1165 (*it)->Remove();
1166 }
1167 SetResult(json_results);
1168 RecordApiFunctions(DOWNLOADS_FUNCTION_ERASE);
1169 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001170}
1171
Ben Murdoch558790d2013-07-30 15:19:42 +01001172DownloadsRemoveFileFunction::DownloadsRemoveFileFunction() {}
1173
1174DownloadsRemoveFileFunction::~DownloadsRemoveFileFunction() {
1175 if (item_) {
1176 item_->RemoveObserver(this);
1177 item_ = NULL;
1178 }
1179}
1180
1181bool DownloadsRemoveFileFunction::RunImpl() {
1182 scoped_ptr<extensions::api::downloads::RemoveFile::Params> params(
1183 extensions::api::downloads::RemoveFile::Params::Create(*args_));
1184 EXTENSION_FUNCTION_VALIDATE(params.get());
1185 DownloadItem* download_item = GetDownload(
1186 profile(), include_incognito(), params->download_id);
1187 if (InvalidId(download_item, &error_) ||
1188 Fault((download_item->GetState() != DownloadItem::COMPLETE),
1189 errors::kNotComplete, &error_) ||
1190 Fault(download_item->GetFileExternallyRemoved(),
1191 errors::kFileAlreadyDeleted, &error_))
1192 return false;
1193 item_ = download_item;
1194 item_->AddObserver(this);
1195 RecordApiFunctions(DOWNLOADS_FUNCTION_REMOVE_FILE);
1196 download_item->DeleteFile();
1197 return true;
1198}
1199
1200void DownloadsRemoveFileFunction::OnDownloadUpdated(DownloadItem* download) {
1201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1202 DCHECK_EQ(item_, download);
1203 if (!item_->GetFileExternallyRemoved())
1204 return;
1205 item_->RemoveObserver(this);
1206 item_ = NULL;
1207 SendResponse(true);
1208}
1209
1210void DownloadsRemoveFileFunction::OnDownloadDestroyed(DownloadItem* download) {
1211 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1212 DCHECK_EQ(item_, download);
1213 item_->RemoveObserver(this);
1214 item_ = NULL;
1215 SendResponse(true);
1216}
1217
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001218DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001219
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001220DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {}
1221
1222bool DownloadsAcceptDangerFunction::RunImpl() {
1223 scoped_ptr<extensions::api::downloads::AcceptDanger::Params> params(
1224 extensions::api::downloads::AcceptDanger::Params::Create(*args_));
1225 EXTENSION_FUNCTION_VALIDATE(params.get());
Ben Murdoch558790d2013-07-30 15:19:42 +01001226 DownloadItem* download_item = GetDownload(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001227 profile(), include_incognito(), params->download_id);
1228 content::WebContents* web_contents =
Ben Murdocheb525c52013-07-10 11:40:50 +01001229 dispatcher()->delegate()->GetVisibleWebContents();
Ben Murdoch558790d2013-07-30 15:19:42 +01001230 if (InvalidId(download_item, &error_) ||
1231 Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
1232 errors::kNotInProgress, &error_) ||
1233 Fault(!download_item->IsDangerous(), errors::kNotDangerous, &error_) ||
1234 Fault(!web_contents, errors::kInvisibleContext, &error_))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001235 return false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001236 RecordApiFunctions(DOWNLOADS_FUNCTION_ACCEPT_DANGER);
1237 // DownloadDangerPrompt displays a modal dialog using native widgets that the
1238 // user must either accept or cancel. It cannot be scripted.
1239 DownloadDangerPrompt::Create(
1240 download_item,
1241 web_contents,
1242 true,
1243 base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback,
Ben Murdoch558790d2013-07-30 15:19:42 +01001244 this, params->download_id));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001245 // DownloadDangerPrompt deletes itself
1246 return true;
1247}
1248
1249void DownloadsAcceptDangerFunction::DangerPromptCallback(
Ben Murdoch558790d2013-07-30 15:19:42 +01001250 int download_id, DownloadDangerPrompt::Action action) {
1251 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1252 DownloadItem* download_item = GetDownload(
1253 profile(), include_incognito(), download_id);
1254 if (InvalidId(download_item, &error_) ||
1255 Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
1256 errors::kNotInProgress, &error_))
1257 return;
1258 switch (action) {
1259 case DownloadDangerPrompt::ACCEPT:
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001260 download_item->ValidateDangerousDownload();
Ben Murdoch558790d2013-07-30 15:19:42 +01001261 break;
1262 case DownloadDangerPrompt::CANCEL:
1263 download_item->Remove();
1264 break;
1265 case DownloadDangerPrompt::DISMISS:
1266 break;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001267 }
1268 SendResponse(error_.empty());
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001269}
1270
1271DownloadsShowFunction::DownloadsShowFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001272
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001273DownloadsShowFunction::~DownloadsShowFunction() {}
1274
1275bool DownloadsShowFunction::RunImpl() {
1276 scoped_ptr<extensions::api::downloads::Show::Params> params(
1277 extensions::api::downloads::Show::Params::Create(*args_));
1278 EXTENSION_FUNCTION_VALIDATE(params.get());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001279 DownloadItem* download_item = GetDownload(
1280 profile(), include_incognito(), params->download_id);
Ben Murdoch558790d2013-07-30 15:19:42 +01001281 if (InvalidId(download_item, &error_))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001282 return false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001283 download_item->ShowDownloadInShell();
1284 RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW);
1285 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001286}
1287
Ben Murdoch558790d2013-07-30 15:19:42 +01001288DownloadsShowDefaultFolderFunction::DownloadsShowDefaultFolderFunction() {}
1289
1290DownloadsShowDefaultFolderFunction::~DownloadsShowDefaultFolderFunction() {}
1291
1292bool DownloadsShowDefaultFolderFunction::RunImpl() {
1293 DownloadManager* manager = NULL;
1294 DownloadManager* incognito_manager = NULL;
1295 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
1296 platform_util::OpenItem(DownloadPrefs::FromDownloadManager(
1297 manager)->DownloadPath());
1298 RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER);
1299 return true;
1300}
1301
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001302DownloadsOpenFunction::DownloadsOpenFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001303
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001304DownloadsOpenFunction::~DownloadsOpenFunction() {}
1305
1306bool DownloadsOpenFunction::RunImpl() {
1307 scoped_ptr<extensions::api::downloads::Open::Params> params(
1308 extensions::api::downloads::Open::Params::Create(*args_));
1309 EXTENSION_FUNCTION_VALIDATE(params.get());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001310 DownloadItem* download_item = GetDownload(
1311 profile(), include_incognito(), params->download_id);
Ben Murdoch558790d2013-07-30 15:19:42 +01001312 if (InvalidId(download_item, &error_) ||
1313 Fault(download_item->GetState() != DownloadItem::COMPLETE,
1314 errors::kNotComplete, &error_) ||
1315 Fault(!GetExtension()->HasAPIPermission(
1316 extensions::APIPermission::kDownloadsOpen),
1317 errors::kOpenPermission, &error_))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001318 return false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001319 download_item->OpenDownload();
1320 RecordApiFunctions(DOWNLOADS_FUNCTION_OPEN);
1321 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001322}
1323
1324DownloadsDragFunction::DownloadsDragFunction() {}
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001325
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001326DownloadsDragFunction::~DownloadsDragFunction() {}
1327
1328bool DownloadsDragFunction::RunImpl() {
1329 scoped_ptr<extensions::api::downloads::Drag::Params> params(
1330 extensions::api::downloads::Drag::Params::Create(*args_));
1331 EXTENSION_FUNCTION_VALIDATE(params.get());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001332 DownloadItem* download_item = GetDownload(
1333 profile(), include_incognito(), params->download_id);
1334 content::WebContents* web_contents =
Ben Murdocheb525c52013-07-10 11:40:50 +01001335 dispatcher()->delegate()->GetVisibleWebContents();
Ben Murdoch558790d2013-07-30 15:19:42 +01001336 if (InvalidId(download_item, &error_) ||
1337 Fault(!web_contents, errors::kInvisibleContext, &error_))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001338 return false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001339 RecordApiFunctions(DOWNLOADS_FUNCTION_DRAG);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001340 gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001341 download_item->GetTargetFilePath(), IconLoader::NORMAL);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001342 gfx::NativeView view = web_contents->GetView()->GetNativeView();
1343 {
1344 // Enable nested tasks during DnD, while |DragDownload()| blocks.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001345 base::MessageLoop::ScopedNestableTaskAllower allow(
1346 base::MessageLoop::current());
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001347 download_util::DragDownload(download_item, icon, view);
1348 }
1349 return true;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001350}
1351
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001352DownloadsSetShelfEnabledFunction::DownloadsSetShelfEnabledFunction() {}
1353
1354DownloadsSetShelfEnabledFunction::~DownloadsSetShelfEnabledFunction() {}
1355
1356bool DownloadsSetShelfEnabledFunction::RunImpl() {
1357 scoped_ptr<extensions::api::downloads::SetShelfEnabled::Params> params(
1358 extensions::api::downloads::SetShelfEnabled::Params::Create(*args_));
1359 EXTENSION_FUNCTION_VALIDATE(params.get());
1360 if (!GetExtension()->HasAPIPermission(
1361 extensions::APIPermission::kDownloadsShelf)) {
1362 error_ = download_extension_errors::kShelfPermission;
1363 return false;
1364 }
1365
1366 RecordApiFunctions(DOWNLOADS_FUNCTION_SET_SHELF_ENABLED);
1367 DownloadManager* manager = NULL;
1368 DownloadManager* incognito_manager = NULL;
1369 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
1370 DownloadService* service = NULL;
1371 DownloadService* incognito_service = NULL;
1372 if (manager) {
1373 service = DownloadServiceFactory::GetForBrowserContext(
1374 manager->GetBrowserContext());
1375 service->GetExtensionEventRouter()->SetShelfEnabled(
1376 GetExtension(), params->enabled);
1377 }
1378 if (incognito_manager) {
1379 incognito_service = DownloadServiceFactory::GetForBrowserContext(
1380 incognito_manager->GetBrowserContext());
1381 incognito_service->GetExtensionEventRouter()->SetShelfEnabled(
1382 GetExtension(), params->enabled);
1383 }
1384
1385 BrowserList* browsers = BrowserList::GetInstance(chrome::GetActiveDesktop());
1386 if (browsers) {
1387 for (BrowserList::const_iterator iter = browsers->begin();
1388 iter != browsers->end(); ++iter) {
1389 const Browser* browser = *iter;
1390 DownloadService* current_service =
1391 DownloadServiceFactory::GetForBrowserContext(browser->profile());
1392 if (((current_service == service) ||
1393 (current_service == incognito_service)) &&
1394 browser->window()->IsDownloadShelfVisible() &&
1395 !current_service->IsShelfEnabled())
1396 browser->window()->GetDownloadShelf()->Close(DownloadShelf::AUTOMATIC);
1397 }
1398 }
1399
1400 if (params->enabled &&
1401 ((manager && !service->IsShelfEnabled()) ||
1402 (incognito_manager && !incognito_service->IsShelfEnabled()))) {
1403 error_ = download_extension_errors::kShelfDisabled;
1404 return false;
1405 }
1406
1407 return true;
1408}
1409
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001410DownloadsGetFileIconFunction::DownloadsGetFileIconFunction()
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001411 : icon_extractor_(new DownloadFileIconExtractorImpl()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001412}
1413
1414DownloadsGetFileIconFunction::~DownloadsGetFileIconFunction() {}
1415
1416void DownloadsGetFileIconFunction::SetIconExtractorForTesting(
1417 DownloadFileIconExtractor* extractor) {
1418 DCHECK(extractor);
1419 icon_extractor_.reset(extractor);
1420}
1421
1422bool DownloadsGetFileIconFunction::RunImpl() {
1423 scoped_ptr<extensions::api::downloads::GetFileIcon::Params> params(
1424 extensions::api::downloads::GetFileIcon::Params::Create(*args_));
1425 EXTENSION_FUNCTION_VALIDATE(params.get());
1426 const extensions::api::downloads::GetFileIconOptions* options =
1427 params->options.get();
1428 int icon_size = kDefaultIconSize;
1429 if (options && options->size.get())
1430 icon_size = *options->size.get();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001431 DownloadItem* download_item = GetDownload(
1432 profile(), include_incognito(), params->download_id);
Ben Murdoch558790d2013-07-30 15:19:42 +01001433 if (InvalidId(download_item, &error_) ||
1434 Fault(download_item->GetTargetFilePath().empty(),
1435 errors::kEmptyFile, &error_))
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001436 return false;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001437 // In-progress downloads return the intermediate filename for GetFullPath()
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001438 // which doesn't have the final extension. Therefore a good file icon can't be
1439 // found, so use GetTargetFilePath() instead.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001440 DCHECK(icon_extractor_.get());
1441 DCHECK(icon_size == 16 || icon_size == 32);
1442 EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath(
1443 download_item->GetTargetFilePath(),
1444 IconLoaderSizeFromPixelSize(icon_size),
1445 base::Bind(&DownloadsGetFileIconFunction::OnIconURLExtracted, this)));
1446 return true;
1447}
1448
1449void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
1450 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Ben Murdoch558790d2013-07-30 15:19:42 +01001451 if (Fault(url.empty(), errors::kIconNotFound, &error_)) {
1452 SendResponse(false);
1453 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001454 }
Ben Murdoch558790d2013-07-30 15:19:42 +01001455 RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON);
1456 SetResult(base::Value::CreateStringValue(url));
1457 SendResponse(true);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001458}
1459
1460ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(
1461 Profile* profile,
1462 DownloadManager* manager)
1463 : profile_(profile),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001464 notifier_(manager, this) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001465 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1466 DCHECK(profile_);
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001467 registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
1468 content::Source<Profile>(profile_));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001469 extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)->
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001470 event_router();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001471 if (router)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001472 router->RegisterObserver(this, events::kOnDownloadDeterminingFilename);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001473}
1474
1475ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() {
1476 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001477 extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)->
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001478 event_router();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001479 if (router)
1480 router->UnregisterObserver(this);
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001481}
1482
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001483void ExtensionDownloadsEventRouter::SetShelfEnabled(
1484 const extensions::Extension* extension, bool enabled) {
1485 std::set<const extensions::Extension*>::iterator iter =
1486 shelf_disabling_extensions_.find(extension);
1487 if (iter == shelf_disabling_extensions_.end()) {
1488 if (!enabled)
1489 shelf_disabling_extensions_.insert(extension);
1490 } else if (enabled) {
1491 shelf_disabling_extensions_.erase(extension);
1492 }
1493}
1494
1495bool ExtensionDownloadsEventRouter::IsShelfEnabled() const {
1496 return shelf_disabling_extensions_.empty();
1497}
1498
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001499// The method by which extensions hook into the filename determination process
1500// is based on the method by which the omnibox API allows extensions to hook
1501// into the omnibox autocompletion process. Extensions that wish to play a part
1502// in the filename determination process call
1503// chrome.downloads.onDeterminingFilename.addListener, which adds an
1504// EventListener object to ExtensionEventRouter::listeners().
1505//
1506// When a download's filename is being determined,
1507// ChromeDownloadManagerDelegate::CheckVisitedReferrerBeforeDone (CVRBD) passes
1508// 2 callbacks to ExtensionDownloadsEventRouter::OnDeterminingFilename (ODF),
1509// which stores the callbacks in the item's ExtensionDownloadsEventRouterData
1510// (EDERD) along with all of the extension IDs that are listening for
1511// onDeterminingFilename events. ODF dispatches
1512// chrome.downloads.onDeterminingFilename.
1513//
1514// When the extension's event handler calls |suggestCallback|,
1515// downloads_custom_bindings.js calls
1516// DownloadsInternalDetermineFilenameFunction::RunImpl, which calls
1517// EDER::DetermineFilename, which notifies the item's EDERD.
1518//
1519// When the last extension's event handler returns, EDERD calls one of the two
1520// callbacks that CVRBD passed to ODF, allowing CDMD to complete the filename
1521// determination process. If multiple extensions wish to override the filename,
1522// then the extension that was last installed wins.
1523
1524void ExtensionDownloadsEventRouter::OnDeterminingFilename(
1525 DownloadItem* item,
1526 const base::FilePath& suggested_path,
1527 const base::Closure& no_change,
1528 const FilenameChangedCallback& change) {
1529 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1530 ExtensionDownloadsEventRouterData* data =
1531 ExtensionDownloadsEventRouterData::Get(item);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001532 if (!data) {
1533 no_change.Run();
1534 return;
1535 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001536 data->ClearPendingDeterminers();
1537 data->set_filename_change_callbacks(no_change, change);
1538 bool any_determiners = false;
1539 base::DictionaryValue* json = DownloadItemToJSON(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001540 item, profile_).release();
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001541 json->SetString(kFilenameKey, suggested_path.LossyDisplayName());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001542 DispatchEvent(events::kOnDownloadDeterminingFilename,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001543 false,
1544 base::Bind(&OnDeterminingFilenameWillDispatchCallback,
1545 &any_determiners,
1546 data),
1547 json);
1548 if (!any_determiners) {
1549 data->ClearPendingDeterminers();
Ben Murdoch558790d2013-07-30 15:19:42 +01001550 if (!data->creator_suggested_filename().empty()) {
1551 change.Run(data->creator_suggested_filename(),
1552 ConvertConflictAction(data->creator_conflict_action()));
1553 // If all listeners are removed, don't keep |data| around.
1554 data->ResetCreatorSuggestion();
1555 } else {
1556 no_change.Run();
1557 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001558 }
1559}
1560
1561bool ExtensionDownloadsEventRouter::DetermineFilename(
1562 Profile* profile,
1563 bool include_incognito,
1564 const std::string& ext_id,
1565 int download_id,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001566 const base::FilePath& const_filename,
1567 extensions::api::downloads::FilenameConflictAction conflict_action,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001568 std::string* error) {
1569 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1570 DownloadItem* item = GetDownload(profile, include_incognito, download_id);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001571 ExtensionDownloadsEventRouterData* data =
Ben Murdoch558790d2013-07-30 15:19:42 +01001572 item ? ExtensionDownloadsEventRouterData::Get(item) : NULL;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001573 // maxListeners=1 in downloads.idl and suggestCallback in
1574 // downloads_custom_bindings.js should prevent duplicate DeterminerCallback
1575 // calls from the same renderer, but an extension may have more than one
1576 // renderer, so don't DCHECK(!reported).
Ben Murdoch558790d2013-07-30 15:19:42 +01001577 if (InvalidId(item, error) ||
1578 Fault(item->GetState() != DownloadItem::IN_PROGRESS,
1579 errors::kNotInProgress, error) ||
1580 Fault(!data, errors::kUnexpectedDeterminer, error) ||
1581 Fault(data->DeterminerAlreadyReported(ext_id),
1582 errors::kTooManyListeners, error))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001583 return false;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001584 base::FilePath::StringType filename_str(const_filename.value());
1585 // Allow windows-style directory separators on all platforms.
1586 std::replace(filename_str.begin(), filename_str.end(),
1587 FILE_PATH_LITERAL('\\'), FILE_PATH_LITERAL('/'));
1588 base::FilePath filename(filename_str);
1589 bool valid_filename = net::IsSafePortableRelativePath(filename);
1590 filename = (valid_filename ? filename.NormalizePathSeparators() :
1591 base::FilePath());
Ben Murdoch558790d2013-07-30 15:19:42 +01001592 // If the invalid filename check is moved to before DeterminerCallback(), then
1593 // it will block forever waiting for this ext_id to report.
1594 if (Fault(!data->DeterminerCallback(ext_id, filename, conflict_action),
1595 errors::kUnexpectedDeterminer, error) ||
1596 Fault((!const_filename.empty() && !valid_filename),
1597 errors::kInvalidFilename, error))
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001598 return false;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001599 return true;
1600}
1601
1602void ExtensionDownloadsEventRouter::OnListenerRemoved(
1603 const extensions::EventListenerInfo& details) {
1604 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001605 DownloadManager* manager = notifier_.GetManager();
1606 if (!manager)
1607 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001608 bool determiner_removed = (
1609 details.event_name == events::kOnDownloadDeterminingFilename);
1610 extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)->
1611 event_router();
1612 bool any_listeners =
1613 router->HasEventListener(events::kOnDownloadChanged) ||
1614 router->HasEventListener(events::kOnDownloadDeterminingFilename);
1615 if (!determiner_removed && any_listeners)
1616 return;
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001617 DownloadManager::DownloadVector items;
1618 manager->GetAllDownloads(&items);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001619 for (DownloadManager::DownloadVector::const_iterator iter =
1620 items.begin();
1621 iter != items.end(); ++iter) {
1622 ExtensionDownloadsEventRouterData* data =
1623 ExtensionDownloadsEventRouterData::Get(*iter);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001624 if (!data)
1625 continue;
1626 if (determiner_removed) {
1627 // Notify any items that may be waiting for callbacks from this
1628 // extension/determiner. This will almost always be a no-op, however, it
1629 // is possible for an extension renderer to be unloaded while a download
1630 // item is waiting for a determiner. In that case, the download item
1631 // should proceed.
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001632 data->DeterminerRemoved(details.extension_id);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001633 }
Ben Murdoch558790d2013-07-30 15:19:42 +01001634 if (!any_listeners &&
1635 data->creator_suggested_filename().empty()) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001636 ExtensionDownloadsEventRouterData::Remove(*iter);
1637 }
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001638 }
1639}
1640
1641// That's all the methods that have to do with filename determination. The rest
1642// have to do with the other, less special events.
1643
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001644void ExtensionDownloadsEventRouter::OnDownloadCreated(
1645 DownloadManager* manager, DownloadItem* download_item) {
1646 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1647 if (download_item->IsTemporary())
1648 return;
1649
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001650 extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)->
1651 event_router();
1652 // Avoid allocating a bunch of memory in DownloadItemToJSON if it isn't going
1653 // to be used.
1654 if (!router ||
1655 (!router->HasEventListener(events::kOnDownloadCreated) &&
1656 !router->HasEventListener(events::kOnDownloadChanged) &&
1657 !router->HasEventListener(events::kOnDownloadDeterminingFilename))) {
1658 return;
1659 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001660 scoped_ptr<base::DictionaryValue> json_item(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001661 DownloadItemToJSON(download_item, profile_));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001662 DispatchEvent(events::kOnDownloadCreated,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001663 true,
1664 extensions::Event::WillDispatchCallback(),
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001665 json_item->DeepCopy());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001666 if (!ExtensionDownloadsEventRouterData::Get(download_item) &&
1667 (router->HasEventListener(events::kOnDownloadChanged) ||
1668 router->HasEventListener(events::kOnDownloadDeterminingFilename))) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001669 new ExtensionDownloadsEventRouterData(download_item, json_item.Pass());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001670 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001671}
1672
1673void ExtensionDownloadsEventRouter::OnDownloadUpdated(
1674 DownloadManager* manager, DownloadItem* download_item) {
1675 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001676 extensions::EventRouter* router = extensions::ExtensionSystem::Get(profile_)->
1677 event_router();
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001678 ExtensionDownloadsEventRouterData* data =
1679 ExtensionDownloadsEventRouterData::Get(download_item);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001680 if (download_item->IsTemporary() ||
1681 !router->HasEventListener(events::kOnDownloadChanged)) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001682 return;
1683 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001684 if (!data) {
1685 // The download_item probably transitioned from temporary to not temporary,
1686 // or else an event listener was added.
1687 data = new ExtensionDownloadsEventRouterData(
1688 download_item,
1689 scoped_ptr<base::DictionaryValue>(new base::DictionaryValue()));
1690 }
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001691 scoped_ptr<base::DictionaryValue> new_json(DownloadItemToJSON(
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001692 download_item, profile_));
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001693 scoped_ptr<base::DictionaryValue> delta(new base::DictionaryValue());
1694 delta->SetInteger(kIdKey, download_item->GetId());
1695 std::set<std::string> new_fields;
1696 bool changed = false;
1697
1698 // For each field in the new json representation of the download_item except
1699 // the bytesReceived field, if the field has changed from the previous old
1700 // json, set the differences in the |delta| object and remember that something
1701 // significant changed.
1702 for (base::DictionaryValue::Iterator iter(*new_json.get());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001703 !iter.IsAtEnd(); iter.Advance()) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001704 new_fields.insert(iter.key());
Ben Murdoch558790d2013-07-30 15:19:42 +01001705 if (IsDownloadDeltaField(iter.key())) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001706 const base::Value* old_value = NULL;
1707 if (!data->json().HasKey(iter.key()) ||
1708 (data->json().Get(iter.key(), &old_value) &&
1709 !iter.value().Equals(old_value))) {
1710 delta->Set(iter.key() + ".current", iter.value().DeepCopy());
1711 if (old_value)
1712 delta->Set(iter.key() + ".previous", old_value->DeepCopy());
1713 changed = true;
1714 }
1715 }
1716 }
1717
1718 // If a field was in the previous json but is not in the new json, set the
1719 // difference in |delta|.
1720 for (base::DictionaryValue::Iterator iter(data->json());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001721 !iter.IsAtEnd(); iter.Advance()) {
Ben Murdoch558790d2013-07-30 15:19:42 +01001722 if ((new_fields.find(iter.key()) == new_fields.end()) &&
1723 IsDownloadDeltaField(iter.key())) {
1724 // estimatedEndTime disappears after completion, but bytesReceived stays.
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001725 delta->Set(iter.key() + ".previous", iter.value().DeepCopy());
1726 changed = true;
1727 }
1728 }
1729
1730 // Update the OnChangedStat and dispatch the event if something significant
1731 // changed. Replace the stored json with the new json.
1732 data->OnItemUpdated();
1733 if (changed) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001734 DispatchEvent(events::kOnDownloadChanged,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001735 true,
1736 extensions::Event::WillDispatchCallback(),
1737 delta.release());
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001738 data->OnChangedFired();
1739 }
1740 data->set_json(new_json.Pass());
1741}
1742
1743void ExtensionDownloadsEventRouter::OnDownloadRemoved(
1744 DownloadManager* manager, DownloadItem* download_item) {
1745 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1746 if (download_item->IsTemporary())
1747 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001748 DispatchEvent(events::kOnDownloadErased,
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001749 true,
1750 extensions::Event::WillDispatchCallback(),
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001751 base::Value::CreateIntegerValue(download_item->GetId()));
1752}
1753
1754void ExtensionDownloadsEventRouter::DispatchEvent(
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001755 const char* event_name,
1756 bool include_incognito,
1757 const extensions::Event::WillDispatchCallback& will_dispatch_callback,
1758 base::Value* arg) {
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001759 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001760 if (!extensions::ExtensionSystem::Get(profile_)->event_router())
1761 return;
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001762 scoped_ptr<base::ListValue> args(new base::ListValue());
1763 args->Append(arg);
1764 std::string json_args;
1765 base::JSONWriter::Write(args.get(), &json_args);
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001766 scoped_ptr<extensions::Event> event(new extensions::Event(
1767 event_name, args.Pass()));
1768 // The downloads system wants to share on-record events with off-record
1769 // extension renderers even in incognito_split_mode because that's how
1770 // chrome://downloads works. The "restrict_to_profile" mechanism does not
1771 // anticipate this, so it does not automatically prevent sharing off-record
1772 // events with on-record extension renderers.
1773 event->restrict_to_profile =
1774 (include_incognito && !profile_->IsOffTheRecord()) ? NULL : profile_;
1775 event->will_dispatch_callback = will_dispatch_callback;
1776 extensions::ExtensionSystem::Get(profile_)->event_router()->
1777 BroadcastEvent(event.Pass());
1778 DownloadsNotificationSource notification_source;
1779 notification_source.event_name = event_name;
1780 notification_source.profile = profile_;
1781 content::Source<DownloadsNotificationSource> content_source(
1782 &notification_source);
1783 content::NotificationService::current()->Notify(
1784 chrome::NOTIFICATION_EXTENSION_DOWNLOADS_EVENT,
1785 content_source,
1786 content::Details<std::string>(&json_args));
Torne (Richard Coles)58218062012-11-14 11:43:16 +00001787}
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +01001788
1789void ExtensionDownloadsEventRouter::Observe(
1790 int type,
1791 const content::NotificationSource& source,
1792 const content::NotificationDetails& details) {
1793 switch (type) {
1794 case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
1795 extensions::UnloadedExtensionInfo* unloaded =
1796 content::Details<extensions::UnloadedExtensionInfo>(details).ptr();
1797 std::set<const extensions::Extension*>::iterator iter =
1798 shelf_disabling_extensions_.find(unloaded->extension);
1799 if (iter != shelf_disabling_extensions_.end())
1800 shelf_disabling_extensions_.erase(iter);
1801 break;
1802 }
1803 }
1804}