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

Side by Side Diff: chrome/browser/download/download_extension_api.cc

Issue 9617010: Move chrome.downloads out of experimental to dev (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: merge Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // 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/download/download_extension_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"
17 #include "base/json/json_writer.h"
18 #include "base/lazy_instance.h"
19 #include "base/logging.h"
20 #include "base/metrics/histogram.h"
21 #include "base/stl_util.h"
22 #include "base/string16.h"
23 #include "base/string_split.h"
24 #include "base/string_util.h"
25 #include "base/stringprintf.h"
26 #include "base/values.h"
27 #include "chrome/browser/browser_process.h"
28 #include "chrome/browser/download/download_file_icon_extractor.h"
29 #include "chrome/browser/download/download_query.h"
30 #include "chrome/browser/download/download_service.h"
31 #include "chrome/browser/download/download_service_factory.h"
32 #include "chrome/browser/download/download_util.h"
33 #include "chrome/browser/extensions/extension_event_names.h"
34 #include "chrome/browser/extensions/extension_event_router.h"
35 #include "chrome/browser/icon_loader.h"
36 #include "chrome/browser/icon_manager.h"
37 #include "chrome/browser/renderer_host/chrome_render_message_filter.h"
38 #include "chrome/browser/ui/browser_list.h"
39 #include "chrome/browser/ui/webui/web_ui_util.h"
40 #include "content/public/browser/download_interrupt_reasons.h"
41 #include "content/public/browser/download_item.h"
42 #include "content/public/browser/download_save_info.h"
43 #include "content/public/browser/render_process_host.h"
44 #include "content/public/browser/render_view_host.h"
45 #include "content/public/browser/resource_dispatcher_host.h"
46 #include "net/base/load_flags.h"
47 #include "net/http/http_util.h"
48 #include "net/url_request/url_request.h"
49
50 using content::BrowserContext;
51 using content::BrowserThread;
52 using content::DownloadId;
53 using content::DownloadItem;
54 using content::DownloadManager;
55
56 namespace download_extension_errors {
57
58 // Error messages
59 const char kGenericError[] = "I'm afraid I can't do that.";
60 const char kIconNotFoundError[] = "Icon not found.";
61 const char kInvalidDangerTypeError[] = "Invalid danger type";
62 const char kInvalidFilterError[] = "Invalid query filter";
63 const char kInvalidOperationError[] = "Invalid operation.";
64 const char kInvalidOrderByError[] = "Invalid orderBy field";
65 const char kInvalidQueryLimit[] = "Invalid query limit";
66 const char kInvalidStateError[] = "Invalid state";
67 const char kInvalidURLError[] = "Invalid URL.";
68 const char kNotImplementedError[] = "NotImplemented.";
69
70 } // namespace download_extension_errors
71
72 namespace {
73
74 // Default icon size for getFileIcon() in pixels.
75 const int kDefaultIconSize = 32;
76
77 // Parameter keys
78 const char kBodyKey[] = "body";
79 const char kBytesReceivedKey[] = "bytesReceived";
80 const char kDangerAcceptedKey[] = "dangerAccepted";
81 const char kDangerContent[] = "content";
82 const char kDangerFile[] = "file";
83 const char kDangerKey[] = "danger";
84 const char kDangerSafe[] = "safe";
85 const char kDangerUncommon[] = "uncommon";
86 const char kDangerUrl[] = "url";
87 const char kEndTimeKey[] = "endTime";
88 const char kErrorKey[] = "error";
89 const char kFileSizeKey[] = "fileSize";
90 const char kFilenameKey[] = "filename";
91 const char kFilenameRegexKey[] = "filenameRegex";
92 const char kHeaderNameKey[] = "name";
93 const char kHeaderValueKey[] = "value";
94 const char kHeaderBinaryValueKey[] = "binaryValue";
95 const char kHeadersKey[] = "headers";
96 const char kIdKey[] = "id";
97 const char kIncognito[] = "incognito";
98 const char kLimitKey[] = "limit";
99 const char kMethodKey[] = "method";
100 const char kMimeKey[] = "mime";
101 const char kOrderByKey[] = "orderBy";
102 const char kPausedKey[] = "paused";
103 const char kQueryKey[] = "query";
104 const char kSaveAsKey[] = "saveAs";
105 const char kSizeKey[] = "size";
106 const char kStartTimeKey[] = "startTime";
107 const char kStartedAfterKey[] = "startedAfter";
108 const char kStartedBeforeKey[] = "startedBefore";
109 const char kStateComplete[] = "complete";
110 const char kStateInProgress[] = "in_progress";
111 const char kStateInterrupted[] = "interrupted";
112 const char kStateKey[] = "state";
113 const char kTotalBytesKey[] = "totalBytes";
114 const char kTotalBytesGreaterKey[] = "totalBytesGreater";
115 const char kTotalBytesLessKey[] = "totalBytesLess";
116 const char kUrlKey[] = "url";
117 const char kUrlRegexKey[] = "urlRegex";
118
119 // Note: Any change to the danger type strings, should be accompanied by a
120 // corresponding change to {experimental.}downloads.json.
121 const char* kDangerStrings[] = {
122 kDangerSafe,
123 kDangerFile,
124 kDangerUrl,
125 kDangerContent,
126 kDangerSafe,
127 kDangerUncommon,
128 };
129 COMPILE_ASSERT(arraysize(kDangerStrings) == content::DOWNLOAD_DANGER_TYPE_MAX,
130 download_danger_type_enum_changed);
131
132 // Note: Any change to the state strings, should be accompanied by a
133 // corresponding change to {experimental.}downloads.json.
134 const char* kStateStrings[] = {
135 kStateInProgress,
136 kStateComplete,
137 kStateInterrupted,
138 NULL,
139 kStateInterrupted,
140 };
141 COMPILE_ASSERT(arraysize(kStateStrings) == DownloadItem::MAX_DOWNLOAD_STATE,
142 download_item_state_enum_changed);
143
144 const char* DangerString(content::DownloadDangerType danger) {
145 DCHECK(danger >= 0);
146 DCHECK(danger < static_cast<content::DownloadDangerType>(
147 arraysize(kDangerStrings)));
148 return kDangerStrings[danger];
149 }
150
151 content::DownloadDangerType DangerEnumFromString(const std::string& danger) {
152 for (size_t i = 0; i < arraysize(kDangerStrings); ++i) {
153 if (danger == kDangerStrings[i])
154 return static_cast<content::DownloadDangerType>(i);
155 }
156 return content::DOWNLOAD_DANGER_TYPE_MAX;
157 }
158
159 const char* StateString(DownloadItem::DownloadState state) {
160 DCHECK(state >= 0);
161 DCHECK(state < static_cast<DownloadItem::DownloadState>(
162 arraysize(kStateStrings)));
163 DCHECK(state != DownloadItem::REMOVING);
164 return kStateStrings[state];
165 }
166
167 DownloadItem::DownloadState StateEnumFromString(const std::string& state) {
168 for (size_t i = 0; i < arraysize(kStateStrings); ++i) {
169 if ((kStateStrings[i] != NULL) && (state == kStateStrings[i]))
170 return static_cast<DownloadItem::DownloadState>(i);
171 }
172 return DownloadItem::MAX_DOWNLOAD_STATE;
173 }
174
175 bool ValidateFilename(const string16& filename) {
176 // TODO(benjhayden): More robust validation of filename.
177 if ((filename.find('/') != string16::npos) ||
178 (filename.find('\\') != string16::npos))
179 return false;
180 if (filename.size() >= 2u && filename[0] == L'.' && filename[1] == L'.')
181 return false;
182 return true;
183 }
184
185 scoped_ptr<base::DictionaryValue> DownloadItemToJSON(DownloadItem* item) {
186 base::DictionaryValue* json = new base::DictionaryValue();
187 json->SetInteger(kIdKey, item->GetId());
188 json->SetString(kUrlKey, item->GetOriginalUrl().spec());
189 json->SetString(kFilenameKey, item->GetFullPath().LossyDisplayName());
190 json->SetString(kDangerKey, DangerString(item->GetDangerType()));
191 if (item->GetDangerType() != content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
192 json->SetBoolean(kDangerAcceptedKey,
193 item->GetSafetyState() == DownloadItem::DANGEROUS_BUT_VALIDATED);
194 json->SetString(kStateKey, StateString(item->GetState()));
195 json->SetBoolean(kPausedKey, item->IsPaused());
196 json->SetString(kMimeKey, item->GetMimeType());
197 json->SetInteger(kStartTimeKey,
198 (item->GetStartTime() - base::Time::UnixEpoch()).InMilliseconds());
199 json->SetInteger(kBytesReceivedKey, item->GetReceivedBytes());
200 json->SetInteger(kTotalBytesKey, item->GetTotalBytes());
201 json->SetBoolean(kIncognito, item->IsOtr());
202 if (item->GetState() == DownloadItem::INTERRUPTED) {
203 json->SetInteger(kErrorKey, static_cast<int>(item->GetLastReason()));
204 } else if (item->GetState() == DownloadItem::CANCELLED) {
205 json->SetInteger(kErrorKey, static_cast<int>(
206 content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED));
207 }
208 // TODO(benjhayden): Implement endTime and fileSize.
209 // json->SetInteger(kEndTimeKey, -1);
210 json->SetInteger(kFileSizeKey, item->GetTotalBytes());
211 return scoped_ptr<base::DictionaryValue>(json);
212 }
213
214 class DownloadFileIconExtractorImpl : public DownloadFileIconExtractor {
215 public:
216 DownloadFileIconExtractorImpl() {}
217
218 ~DownloadFileIconExtractorImpl() {}
219
220 virtual bool ExtractIconURLForPath(const FilePath& path,
221 IconLoader::IconSize icon_size,
222 IconURLCallback callback) OVERRIDE;
223 private:
224 void OnIconLoadComplete(IconManager::Handle handle, gfx::Image* icon);
225
226 CancelableRequestConsumer cancelable_consumer_;
227 IconURLCallback callback_;
228 };
229
230 bool DownloadFileIconExtractorImpl::ExtractIconURLForPath(
231 const FilePath& path,
232 IconLoader::IconSize icon_size,
233 IconURLCallback callback) {
234 callback_ = callback;
235 IconManager* im = g_browser_process->icon_manager();
236 // The contents of the file at |path| may have changed since a previous
237 // request, in which case the associated icon may also have changed.
238 // Therefore, for the moment we always call LoadIcon instead of attempting
239 // a LookupIcon.
240 im->LoadIcon(
241 path, icon_size, &cancelable_consumer_,
242 base::Bind(&DownloadFileIconExtractorImpl::OnIconLoadComplete,
243 base::Unretained(this)));
244 return true;
245 }
246
247 void DownloadFileIconExtractorImpl::OnIconLoadComplete(
248 IconManager::Handle handle,
249 gfx::Image* icon) {
250 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
251 std::string url;
252 if (icon)
253 url = web_ui_util::GetImageDataUrl(*icon->ToImageSkia());
254 callback_.Run(url);
255 }
256
257 IconLoader::IconSize IconLoaderSizeFromPixelSize(int pixel_size) {
258 switch (pixel_size) {
259 case 16: return IconLoader::SMALL;
260 case 32: return IconLoader::NORMAL;
261 default:
262 NOTREACHED();
263 return IconLoader::NORMAL;
264 }
265 }
266
267 typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap;
268
269 void InitFilterTypeMap(FilterTypeMap& filter_types) {
270 filter_types[kBytesReceivedKey] =
271 DownloadQuery::FILTER_BYTES_RECEIVED;
272 filter_types[kDangerAcceptedKey] =
273 DownloadQuery::FILTER_DANGER_ACCEPTED;
274 filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME;
275 filter_types[kFilenameRegexKey] =
276 DownloadQuery::FILTER_FILENAME_REGEX;
277 filter_types[kMimeKey] = DownloadQuery::FILTER_MIME;
278 filter_types[kPausedKey] = DownloadQuery::FILTER_PAUSED;
279 filter_types[kQueryKey] = DownloadQuery::FILTER_QUERY;
280 filter_types[kStartedAfterKey] = DownloadQuery::FILTER_STARTED_AFTER;
281 filter_types[kStartedBeforeKey] =
282 DownloadQuery::FILTER_STARTED_BEFORE;
283 filter_types[kStartTimeKey] = DownloadQuery::FILTER_START_TIME;
284 filter_types[kTotalBytesKey] = DownloadQuery::FILTER_TOTAL_BYTES;
285 filter_types[kTotalBytesGreaterKey] =
286 DownloadQuery::FILTER_TOTAL_BYTES_GREATER;
287 filter_types[kTotalBytesLessKey] =
288 DownloadQuery::FILTER_TOTAL_BYTES_LESS;
289 filter_types[kUrlKey] = DownloadQuery::FILTER_URL;
290 filter_types[kUrlRegexKey] = DownloadQuery::FILTER_URL_REGEX;
291 }
292
293 typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap;
294
295 void InitSortTypeMap(SortTypeMap& sorter_types) {
296 sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED;
297 sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER;
298 sorter_types[kDangerAcceptedKey] =
299 DownloadQuery::SORT_DANGER_ACCEPTED;
300 sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME;
301 sorter_types[kMimeKey] = DownloadQuery::SORT_MIME;
302 sorter_types[kPausedKey] = DownloadQuery::SORT_PAUSED;
303 sorter_types[kStartTimeKey] = DownloadQuery::SORT_START_TIME;
304 sorter_types[kStateKey] = DownloadQuery::SORT_STATE;
305 sorter_types[kTotalBytesKey] = DownloadQuery::SORT_TOTAL_BYTES;
306 sorter_types[kUrlKey] = DownloadQuery::SORT_URL;
307 }
308
309 bool IsNotTemporaryDownloadFilter(const DownloadItem& item) {
310 return !item.IsTemporary();
311 }
312
313 void GetManagers(
314 Profile* profile,
315 bool include_incognito,
316 DownloadManager** manager, DownloadManager** incognito_manager) {
317 *manager = BrowserContext::GetDownloadManager(profile);
318 *incognito_manager = NULL;
319 if (include_incognito && profile->HasOffTheRecordProfile()) {
320 *incognito_manager = BrowserContext::GetDownloadManager(
321 profile->GetOffTheRecordProfile());
322 }
323 }
324
325 DownloadItem* GetActiveItemInternal(
326 Profile* profile,
327 bool include_incognito,
328 int id) {
329 DownloadManager* manager = NULL;
330 DownloadManager* incognito_manager = NULL;
331 GetManagers(profile, include_incognito, &manager, &incognito_manager);
332 DownloadItem* download_item = manager->GetActiveDownloadItem(id);
333 if (!download_item && incognito_manager)
334 download_item = incognito_manager->GetActiveDownloadItem(id);
335 return download_item;
336 }
337
338 } // namespace
339
340 bool DownloadsFunctionInterface::RunImplImpl(
341 DownloadsFunctionInterface* pimpl) {
342 CHECK(pimpl);
343 if (!pimpl->ParseArgs()) return false;
344 UMA_HISTOGRAM_ENUMERATION(
345 "Download.ApiFunctions", pimpl->function(), DOWNLOADS_FUNCTION_LAST);
346 return pimpl->RunInternal();
347 }
348
349 SyncDownloadsFunction::SyncDownloadsFunction(
350 DownloadsFunctionInterface::DownloadsFunctionName function)
351 : function_(function) {
352 }
353
354 SyncDownloadsFunction::~SyncDownloadsFunction() {}
355
356 bool SyncDownloadsFunction::RunImpl() {
357 return DownloadsFunctionInterface::RunImplImpl(this);
358 }
359
360 DownloadsFunctionInterface::DownloadsFunctionName
361 SyncDownloadsFunction::function() const {
362 return function_;
363 }
364
365 DownloadItem* SyncDownloadsFunction::GetActiveItem(int download_id) {
366 return GetActiveItemInternal(profile(), include_incognito(), download_id);
367 }
368
369 AsyncDownloadsFunction::AsyncDownloadsFunction(
370 DownloadsFunctionInterface::DownloadsFunctionName function)
371 : function_(function) {
372 }
373
374 AsyncDownloadsFunction::~AsyncDownloadsFunction() {}
375
376 bool AsyncDownloadsFunction::RunImpl() {
377 return DownloadsFunctionInterface::RunImplImpl(this);
378 }
379
380 DownloadsFunctionInterface::DownloadsFunctionName
381 AsyncDownloadsFunction::function() const {
382 return function_;
383 }
384
385 DownloadItem* AsyncDownloadsFunction::GetActiveItem(int download_id) {
386 return GetActiveItemInternal(profile(), include_incognito(), download_id);
387 }
388
389 DownloadsDownloadFunction::DownloadsDownloadFunction()
390 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_DOWNLOAD) {
391 }
392
393 DownloadsDownloadFunction::~DownloadsDownloadFunction() {}
394
395 DownloadsDownloadFunction::IOData::IOData()
396 : save_as(false),
397 extra_headers(NULL),
398 method("GET"),
399 rdh(NULL),
400 resource_context(NULL),
401 render_process_host_id(0),
402 render_view_host_routing_id(0) {
403 }
404
405 DownloadsDownloadFunction::IOData::~IOData() {}
406
407 bool DownloadsDownloadFunction::ParseArgs() {
408 base::DictionaryValue* options = NULL;
409 std::string url;
410 iodata_.reset(new IOData());
411 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options));
412 EXTENSION_FUNCTION_VALIDATE(options->GetString(kUrlKey, &url));
413 iodata_->url = GURL(url);
414 if (!iodata_->url.is_valid()) {
415 error_ = download_extension_errors::kInvalidURLError;
416 return false;
417 }
418
419 if (!iodata_->url.SchemeIs("data") &&
420 iodata_->url.GetOrigin() != GetExtension()->url().GetOrigin() &&
421 !GetExtension()->HasHostPermission(iodata_->url)) {
422 error_ = download_extension_errors::kInvalidURLError;
423 return false;
424 }
425
426 if (options->HasKey(kFilenameKey)) {
427 EXTENSION_FUNCTION_VALIDATE(options->GetString(
428 kFilenameKey, &iodata_->filename));
429 if (!ValidateFilename(iodata_->filename)) {
430 error_ = download_extension_errors::kGenericError;
431 return false;
432 }
433 }
434
435 if (options->HasKey(kSaveAsKey)) {
436 EXTENSION_FUNCTION_VALIDATE(options->GetBoolean(
437 kSaveAsKey, &iodata_->save_as));
438 }
439
440 if (options->HasKey(kMethodKey)) {
441 EXTENSION_FUNCTION_VALIDATE(options->GetString(
442 kMethodKey, &iodata_->method));
443 }
444
445 // It's ok to use a pointer to extra_headers without DeepCopy()ing because
446 // |args_| (which owns *extra_headers) is guaranteed to live as long as
447 // |this|.
448 if (options->HasKey(kHeadersKey)) {
449 EXTENSION_FUNCTION_VALIDATE(options->GetList(
450 kHeadersKey, &iodata_->extra_headers));
451 }
452
453 if (options->HasKey(kBodyKey)) {
454 EXTENSION_FUNCTION_VALIDATE(options->GetString(
455 kBodyKey, &iodata_->post_body));
456 }
457
458 if (iodata_->extra_headers != NULL) {
459 for (size_t index = 0; index < iodata_->extra_headers->GetSize(); ++index) {
460 base::DictionaryValue* header = NULL;
461 std::string name;
462 EXTENSION_FUNCTION_VALIDATE(iodata_->extra_headers->GetDictionary(
463 index, &header));
464 EXTENSION_FUNCTION_VALIDATE(header->GetString(
465 kHeaderNameKey, &name));
466 if (header->HasKey(kHeaderBinaryValueKey)) {
467 base::ListValue* binary_value = NULL;
468 EXTENSION_FUNCTION_VALIDATE(header->GetList(
469 kHeaderBinaryValueKey, &binary_value));
470 for (size_t char_i = 0; char_i < binary_value->GetSize(); ++char_i) {
471 int char_value = 0;
472 EXTENSION_FUNCTION_VALIDATE(binary_value->GetInteger(
473 char_i, &char_value));
474 }
475 } else if (header->HasKey(kHeaderValueKey)) {
476 std::string value;
477 EXTENSION_FUNCTION_VALIDATE(header->GetString(
478 kHeaderValueKey, &value));
479 }
480 if (!net::HttpUtil::IsSafeHeader(name)) {
481 error_ = download_extension_errors::kGenericError;
482 return false;
483 }
484 }
485 }
486 iodata_->rdh = content::ResourceDispatcherHost::Get();
487 iodata_->resource_context = profile()->GetResourceContext();
488 iodata_->render_process_host_id = render_view_host()->GetProcess()->GetID();
489 iodata_->render_view_host_routing_id = render_view_host()->GetRoutingID();
490 return true;
491 }
492
493 bool DownloadsDownloadFunction::RunInternal() {
494 VLOG(1) << __FUNCTION__ << " " << iodata_->url.spec();
495 if (!BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(
496 &DownloadsDownloadFunction::BeginDownloadOnIOThread, this))) {
497 error_ = download_extension_errors::kGenericError;
498 return false;
499 }
500 return true;
501 }
502
503 void DownloadsDownloadFunction::BeginDownloadOnIOThread() {
504 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
505 DVLOG(1) << __FUNCTION__ << " " << iodata_->url.spec();
506 content::DownloadSaveInfo save_info;
507 // TODO(benjhayden) Ensure that this filename is interpreted as a path
508 // relative to the default downloads directory without allowing '..'.
509 save_info.suggested_name = iodata_->filename;
510 save_info.prompt_for_save_location = iodata_->save_as;
511
512 scoped_ptr<net::URLRequest> request(new net::URLRequest(iodata_->url, NULL));
513 request->set_method(iodata_->method);
514 if (iodata_->extra_headers != NULL) {
515 for (size_t index = 0; index < iodata_->extra_headers->GetSize(); ++index) {
516 base::DictionaryValue* header = NULL;
517 std::string name, value;
518 CHECK(iodata_->extra_headers->GetDictionary(index, &header));
519 CHECK(header->GetString(kHeaderNameKey, &name));
520 if (header->HasKey(kHeaderBinaryValueKey)) {
521 base::ListValue* binary_value = NULL;
522 CHECK(header->GetList(kHeaderBinaryValueKey, &binary_value));
523 for (size_t char_i = 0; char_i < binary_value->GetSize(); ++char_i) {
524 int char_value = 0;
525 CHECK(binary_value->GetInteger(char_i, &char_value));
526 if ((0 <= char_value) &&
527 (char_value <= 0xff)) {
528 value.push_back(char_value);
529 }
530 }
531 } else if (header->HasKey(kHeaderValueKey)) {
532 CHECK(header->GetString(kHeaderValueKey, &value));
533 }
534 request->SetExtraRequestHeaderByName(name, value, false/*overwrite*/);
535 }
536 }
537 if (!iodata_->post_body.empty()) {
538 request->AppendBytesToUpload(iodata_->post_body.data(),
539 iodata_->post_body.size());
540 }
541
542 // Prevent login prompts for 401/407 responses.
543 request->set_load_flags(request->load_flags() |
544 net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
545
546 net::Error error = iodata_->rdh->BeginDownload(
547 request.Pass(),
548 false, // is_content_initiated
549 iodata_->resource_context,
550 iodata_->render_process_host_id,
551 iodata_->render_view_host_routing_id,
552 false, // prefer_cache
553 save_info,
554 base::Bind(&DownloadsDownloadFunction::OnStarted, this));
555 iodata_.reset();
556
557 if (error != net::OK) {
558 BrowserThread::PostTask(
559 BrowserThread::UI, FROM_HERE,
560 base::Bind(&DownloadsDownloadFunction::OnStarted, this,
561 DownloadId::Invalid(), error));
562 }
563 }
564
565 void DownloadsDownloadFunction::OnStarted(DownloadId dl_id, net::Error error) {
566 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
567 VLOG(1) << __FUNCTION__ << " " << dl_id << " " << error;
568 if (dl_id.local() >= 0) {
569 result_.reset(base::Value::CreateIntegerValue(dl_id.local()));
570 } else {
571 error_ = net::ErrorToString(error);
572 }
573 SendResponse(error_.empty());
574 }
575
576 DownloadsSearchFunction::DownloadsSearchFunction()
577 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_SEARCH),
578 query_(new DownloadQuery()),
579 get_id_(0),
580 has_get_id_(false) {
581 }
582
583 DownloadsSearchFunction::~DownloadsSearchFunction() {}
584
585 bool DownloadsSearchFunction::ParseArgs() {
586 static base::LazyInstance<FilterTypeMap> filter_types =
587 LAZY_INSTANCE_INITIALIZER;
588 if (filter_types.Get().size() == 0)
589 InitFilterTypeMap(filter_types.Get());
590
591 base::DictionaryValue* query_json = NULL;
592 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query_json));
593 for (base::DictionaryValue::Iterator query_json_field(*query_json);
594 query_json_field.HasNext(); query_json_field.Advance()) {
595 FilterTypeMap::const_iterator filter_type =
596 filter_types.Get().find(query_json_field.key());
597
598 if (filter_type != filter_types.Get().end()) {
599 if (!query_->AddFilter(filter_type->second, query_json_field.value())) {
600 error_ = download_extension_errors::kInvalidFilterError;
601 return false;
602 }
603 } else if (query_json_field.key() == kIdKey) {
604 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsInteger(
605 &get_id_));
606 has_get_id_ = true;
607 } else if (query_json_field.key() == kOrderByKey) {
608 if (!ParseOrderBy(query_json_field.value()))
609 return false;
610 } else if (query_json_field.key() == kDangerKey) {
611 std::string danger_str;
612 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsString(
613 &danger_str));
614 content::DownloadDangerType danger_type =
615 DangerEnumFromString(danger_str);
616 if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
617 error_ = download_extension_errors::kInvalidDangerTypeError;
618 return false;
619 }
620 query_->AddFilter(danger_type);
621 } else if (query_json_field.key() == kStateKey) {
622 std::string state_str;
623 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsString(
624 &state_str));
625 DownloadItem::DownloadState state = StateEnumFromString(state_str);
626 if (state == DownloadItem::MAX_DOWNLOAD_STATE) {
627 error_ = download_extension_errors::kInvalidStateError;
628 return false;
629 }
630 query_->AddFilter(state);
631 } else if (query_json_field.key() == kLimitKey) {
632 int limit = 0;
633 EXTENSION_FUNCTION_VALIDATE(query_json_field.value().GetAsInteger(
634 &limit));
635 if (limit < 0) {
636 error_ = download_extension_errors::kInvalidQueryLimit;
637 return false;
638 }
639 query_->Limit(limit);
640 } else {
641 EXTENSION_FUNCTION_VALIDATE(false);
642 }
643 }
644 return true;
645 }
646
647 bool DownloadsSearchFunction::ParseOrderBy(const base::Value& order_by_value) {
648 static base::LazyInstance<SortTypeMap> sorter_types =
649 LAZY_INSTANCE_INITIALIZER;
650 if (sorter_types.Get().size() == 0)
651 InitSortTypeMap(sorter_types.Get());
652
653 std::string order_by_str;
654 EXTENSION_FUNCTION_VALIDATE(order_by_value.GetAsString(&order_by_str));
655 std::vector<std::string> order_by_strs;
656 base::SplitString(order_by_str, ' ', &order_by_strs);
657 for (std::vector<std::string>::const_iterator iter = order_by_strs.begin();
658 iter != order_by_strs.end(); ++iter) {
659 std::string term_str = *iter;
660 if (term_str.empty())
661 continue;
662 DownloadQuery::SortDirection direction = DownloadQuery::ASCENDING;
663 if (term_str[0] == '-') {
664 direction = DownloadQuery::DESCENDING;
665 term_str = term_str.substr(1);
666 }
667 SortTypeMap::const_iterator sorter_type =
668 sorter_types.Get().find(term_str);
669 if (sorter_type == sorter_types.Get().end()) {
670 error_ = download_extension_errors::kInvalidOrderByError;
671 return false;
672 }
673 query_->AddSorter(sorter_type->second, direction);
674 }
675 return true;
676 }
677
678 bool DownloadsSearchFunction::RunInternal() {
679 DownloadManager* manager = NULL;
680 DownloadManager* incognito_manager = NULL;
681 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
682 DownloadQuery::DownloadVector all_items, cpp_results;
683 if (has_get_id_) {
684 DownloadItem* item = manager->GetDownloadItem(get_id_);
685 if (!item && incognito_manager)
686 item = incognito_manager->GetDownloadItem(get_id_);
687 if (item)
688 all_items.push_back(item);
689 } else {
690 manager->GetAllDownloads(FilePath(FILE_PATH_LITERAL("")), &all_items);
691 if (incognito_manager)
692 incognito_manager->GetAllDownloads(
693 FilePath(FILE_PATH_LITERAL("")), &all_items);
694 }
695 query_->Search(all_items.begin(), all_items.end(), &cpp_results);
696 base::ListValue* json_results = new base::ListValue();
697 for (DownloadManager::DownloadVector::const_iterator it = cpp_results.begin();
698 it != cpp_results.end(); ++it) {
699 scoped_ptr<base::DictionaryValue> item(DownloadItemToJSON(*it));
700 json_results->Append(item.release());
701 }
702 result_.reset(json_results);
703 return true;
704 }
705
706 DownloadsPauseFunction::DownloadsPauseFunction()
707 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_PAUSE),
708 download_id_(DownloadId::Invalid().local()) {
709 }
710
711 DownloadsPauseFunction::~DownloadsPauseFunction() {}
712
713 bool DownloadsPauseFunction::ParseArgs() {
714 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_));
715 return true;
716 }
717
718 bool DownloadsPauseFunction::RunInternal() {
719 DownloadItem* download_item = GetActiveItem(download_id_);
720 if ((download_item == NULL) || !download_item->IsInProgress()) {
721 // This could be due to an invalid download ID, or it could be due to the
722 // download not being currently active.
723 error_ = download_extension_errors::kInvalidOperationError;
724 } else if (!download_item->IsPaused()) {
725 // If download_item->IsPaused() already then we treat it as a success.
726 download_item->TogglePause();
727 }
728 return error_.empty();
729 }
730
731 DownloadsResumeFunction::DownloadsResumeFunction()
732 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_RESUME),
733 download_id_(DownloadId::Invalid().local()) {
734 }
735
736 DownloadsResumeFunction::~DownloadsResumeFunction() {}
737
738 bool DownloadsResumeFunction::ParseArgs() {
739 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_));
740 return true;
741 }
742
743 bool DownloadsResumeFunction::RunInternal() {
744 DownloadItem* download_item = GetActiveItem(download_id_);
745 if (download_item == NULL) {
746 // This could be due to an invalid download ID, or it could be due to the
747 // download not being currently active.
748 error_ = download_extension_errors::kInvalidOperationError;
749 } else if (download_item->IsPaused()) {
750 // If !download_item->IsPaused() already, then we treat it as a success.
751 download_item->TogglePause();
752 }
753 return error_.empty();
754 }
755
756 DownloadsCancelFunction::DownloadsCancelFunction()
757 : SyncDownloadsFunction(DOWNLOADS_FUNCTION_CANCEL),
758 download_id_(DownloadId::Invalid().local()) {
759 }
760
761 DownloadsCancelFunction::~DownloadsCancelFunction() {}
762
763 bool DownloadsCancelFunction::ParseArgs() {
764 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &download_id_));
765 return true;
766 }
767
768 bool DownloadsCancelFunction::RunInternal() {
769 DownloadItem* download_item = GetActiveItem(download_id_);
770 if (download_item != NULL)
771 download_item->Cancel(true);
772 // |download_item| can be NULL if the download ID was invalid or if the
773 // download is not currently active. Either way, we don't consider it a
774 // failure.
775 return error_.empty();
776 }
777
778 DownloadsEraseFunction::DownloadsEraseFunction()
779 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_ERASE) {
780 }
781
782 DownloadsEraseFunction::~DownloadsEraseFunction() {}
783
784 bool DownloadsEraseFunction::ParseArgs() {
785 base::DictionaryValue* query_json = NULL;
786 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &query_json));
787 error_ = download_extension_errors::kNotImplementedError;
788 return false;
789 }
790
791 bool DownloadsEraseFunction::RunInternal() {
792 NOTIMPLEMENTED();
793 return false;
794 }
795
796 DownloadsSetDestinationFunction::DownloadsSetDestinationFunction()
797 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_SET_DESTINATION) {
798 }
799
800 DownloadsSetDestinationFunction::~DownloadsSetDestinationFunction() {}
801
802 bool DownloadsSetDestinationFunction::ParseArgs() {
803 int dl_id = 0;
804 std::string path;
805 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
806 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &path));
807 VLOG(1) << __FUNCTION__ << " " << dl_id << " " << &path;
808 error_ = download_extension_errors::kNotImplementedError;
809 return false;
810 }
811
812 bool DownloadsSetDestinationFunction::RunInternal() {
813 NOTIMPLEMENTED();
814 return false;
815 }
816
817 DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction()
818 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_ACCEPT_DANGER) {
819 }
820
821 DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {}
822
823 bool DownloadsAcceptDangerFunction::ParseArgs() {
824 int dl_id = 0;
825 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
826 VLOG(1) << __FUNCTION__ << " " << dl_id;
827 error_ = download_extension_errors::kNotImplementedError;
828 return false;
829 }
830
831 bool DownloadsAcceptDangerFunction::RunInternal() {
832 NOTIMPLEMENTED();
833 return false;
834 }
835
836 DownloadsShowFunction::DownloadsShowFunction()
837 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_SHOW) {
838 }
839
840 DownloadsShowFunction::~DownloadsShowFunction() {}
841
842 bool DownloadsShowFunction::ParseArgs() {
843 int dl_id = 0;
844 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
845 VLOG(1) << __FUNCTION__ << " " << dl_id;
846 error_ = download_extension_errors::kNotImplementedError;
847 return false;
848 }
849
850 bool DownloadsShowFunction::RunInternal() {
851 NOTIMPLEMENTED();
852 return false;
853 }
854
855 DownloadsDragFunction::DownloadsDragFunction()
856 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_DRAG) {
857 }
858
859 DownloadsDragFunction::~DownloadsDragFunction() {}
860
861 bool DownloadsDragFunction::ParseArgs() {
862 int dl_id = 0;
863 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
864 VLOG(1) << __FUNCTION__ << " " << dl_id;
865 error_ = download_extension_errors::kNotImplementedError;
866 return false;
867 }
868
869 bool DownloadsDragFunction::RunInternal() {
870 NOTIMPLEMENTED();
871 return false;
872 }
873
874 DownloadsGetFileIconFunction::DownloadsGetFileIconFunction()
875 : AsyncDownloadsFunction(DOWNLOADS_FUNCTION_GET_FILE_ICON),
876 icon_size_(kDefaultIconSize),
877 icon_extractor_(new DownloadFileIconExtractorImpl()) {
878 }
879
880 DownloadsGetFileIconFunction::~DownloadsGetFileIconFunction() {}
881
882 void DownloadsGetFileIconFunction::SetIconExtractorForTesting(
883 DownloadFileIconExtractor* extractor) {
884 DCHECK(extractor);
885 icon_extractor_.reset(extractor);
886 }
887
888 bool DownloadsGetFileIconFunction::ParseArgs() {
889 int dl_id = 0;
890 EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &dl_id));
891
892 base::DictionaryValue* options = NULL;
893 EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &options));
894 if (options->HasKey(kSizeKey)) {
895 EXTENSION_FUNCTION_VALIDATE(options->GetInteger(kSizeKey, &icon_size_));
896 // We only support 16px and 32px icons. This is enforced in
897 // experimental.downloads.json.
898 DCHECK(icon_size_ == 16 || icon_size_ == 32);
899 }
900
901 DownloadManager* manager = NULL;
902 DownloadManager* incognito_manager = NULL;
903 GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
904 DownloadItem* download_item = manager->GetDownloadItem(dl_id);
905 if (!download_item && incognito_manager)
906 download_item = incognito_manager->GetDownloadItem(dl_id);
907 if (!download_item) {
908 // The DownloadItem is is added to history when the path is determined. If
909 // the download is not in history, then we don't have a path / final
910 // filename and no icon.
911 error_ = download_extension_errors::kInvalidOperationError;
912 return false;
913 }
914 // In-progress downloads return the intermediate filename for GetFullPath()
915 // which doesn't have the final extension. Therefore we won't be able to
916 // derive a good file icon for it. So we use GetTargetFilePath() instead.
917 path_ = download_item->GetTargetFilePath();
918 DCHECK(!path_.empty());
919 return true;
920 }
921
922 bool DownloadsGetFileIconFunction::RunInternal() {
923 DCHECK(!path_.empty());
924 DCHECK(icon_extractor_.get());
925 EXTENSION_FUNCTION_VALIDATE(icon_extractor_->ExtractIconURLForPath(
926 path_, IconLoaderSizeFromPixelSize(icon_size_),
927 base::Bind(&DownloadsGetFileIconFunction::OnIconURLExtracted, this)));
928 return true;
929 }
930
931 void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
932 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
933 if (url.empty())
934 error_ = download_extension_errors::kIconNotFoundError;
935 else
936 result_.reset(base::Value::CreateStringValue(url));
937 SendResponse(error_.empty());
938 }
939
940 ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(Profile* profile)
941 : profile_(profile),
942 manager_(NULL),
943 delete_item_jsons_(&item_jsons_),
944 delete_on_changed_stats_(&on_changed_stats_) {
945 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
946 DCHECK(profile_);
947 // Register a callback with the DownloadService for this profile to be called
948 // when it creates the DownloadManager, or now if the manager already exists.
949 DownloadServiceFactory::GetForProfile(profile)->OnManagerCreated(base::Bind(
950 &ExtensionDownloadsEventRouter::Init, base::Unretained(this)));
951 }
952
953 // The only public methods on this class are ModelChanged() and
954 // ManagerGoingDown(), and they are only called by DownloadManager, so
955 // there's no way for any methods on this class to be called before
956 // DownloadService calls Init() via the OnManagerCreated Callback above.
957 void ExtensionDownloadsEventRouter::Init(DownloadManager* manager) {
958 DCHECK(manager_ == NULL);
959 manager_ = manager;
960 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
961 manager_->AddObserver(this);
962 }
963
964 ExtensionDownloadsEventRouter::~ExtensionDownloadsEventRouter() {
965 if (manager_ != NULL)
966 manager_->RemoveObserver(this);
967 for (ItemMap::const_iterator iter = downloads_.begin();
968 iter != downloads_.end(); ++iter) {
969 if (iter->second != NULL)
970 iter->second->RemoveObserver(this);
971 }
972 }
973
974 ExtensionDownloadsEventRouter::OnChangedStat::OnChangedStat()
975 : fires(0),
976 total(0) {
977 }
978
979 ExtensionDownloadsEventRouter::OnChangedStat::~OnChangedStat() {
980 if (total > 0)
981 UMA_HISTOGRAM_PERCENTAGE("Download.OnChanged", (fires * 100 / total));
982 }
983
984 void ExtensionDownloadsEventRouter::OnDownloadUpdated(DownloadItem* item) {
985 int download_id = item->GetId();
986 if (item->GetState() == DownloadItem::REMOVING) {
987 // The REMOVING state indicates that this item is being erased.
988 // Let's unregister as an observer so that we don't see any more updates
989 // from it, dispatch the onErased event, and remove its json and is
990 // OnChangedStat from our maps.
991 downloads_.erase(download_id);
992 item->RemoveObserver(this);
993 DispatchEvent(extension_event_names::kOnDownloadErased,
994 base::Value::CreateIntegerValue(download_id));
995 delete item_jsons_[download_id];
996 item_jsons_.erase(download_id);
997 delete on_changed_stats_[download_id];
998 on_changed_stats_.erase(download_id);
999 return;
1000 }
1001
1002 base::DictionaryValue* old_json = item_jsons_[download_id];
1003 scoped_ptr<base::DictionaryValue> new_json(DownloadItemToJSON(item));
1004 scoped_ptr<base::DictionaryValue> delta(new base::DictionaryValue());
1005 delta->SetInteger(kIdKey, download_id);
1006 std::set<std::string> new_fields;
1007 bool changed = false;
1008
1009 // For each field in the new json representation of the item except the
1010 // bytesReceived field, if the field has changed from the previous old json,
1011 // set the differences in the |delta| object and remember that something
1012 // significant changed.
1013 for (base::DictionaryValue::Iterator iter(*new_json.get());
1014 iter.HasNext(); iter.Advance()) {
1015 new_fields.insert(iter.key());
1016 if (iter.key() != kBytesReceivedKey) {
1017 base::Value* old_value = NULL;
1018 if (!old_json->HasKey(iter.key()) ||
1019 (old_json->Get(iter.key(), &old_value) &&
1020 !iter.value().Equals(old_value))) {
1021 delta->Set(iter.key() + ".new", iter.value().DeepCopy());
1022 if (old_value)
1023 delta->Set(iter.key() + ".old", old_value->DeepCopy());
1024 changed = true;
1025 }
1026 }
1027 }
1028
1029 // If a field was in the previous json but is not in the new json, set the
1030 // difference in |delta|.
1031 for (base::DictionaryValue::Iterator iter(*old_json);
1032 iter.HasNext(); iter.Advance()) {
1033 if (new_fields.find(iter.key()) == new_fields.end()) {
1034 delta->Set(iter.key() + ".old", iter.value().DeepCopy());
1035 changed = true;
1036 }
1037 }
1038
1039 // Update the OnChangedStat and dispatch the event if something significant
1040 // changed. Replace the stored json with the new json.
1041 ++(on_changed_stats_[download_id]->total);
1042 if (changed) {
1043 DispatchEvent(extension_event_names::kOnDownloadChanged, delta.release());
1044 ++(on_changed_stats_[download_id]->fires);
1045 }
1046 item_jsons_[download_id]->Swap(new_json.get());
1047 }
1048
1049 void ExtensionDownloadsEventRouter::OnDownloadOpened(DownloadItem* item) {
1050 }
1051
1052 void ExtensionDownloadsEventRouter::ModelChanged(DownloadManager* manager) {
1053 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
1054 DCHECK(manager_ == manager);
1055 typedef std::set<int> DownloadIdSet;
1056
1057 // Get all the download items.
1058 DownloadManager::DownloadVector current_vec;
1059 manager_->SearchDownloads(string16(), &current_vec);
1060
1061 // Populate set<>s of download item identifiers so that we can find
1062 // differences between the old and the new set of download items.
1063 DownloadIdSet current_set, prev_set;
1064 for (ItemMap::const_iterator iter = downloads_.begin();
1065 iter != downloads_.end(); ++iter) {
1066 prev_set.insert(iter->first);
1067 }
1068 ItemMap current_map;
1069 for (DownloadManager::DownloadVector::const_iterator iter =
1070 current_vec.begin();
1071 iter != current_vec.end(); ++iter) {
1072 DownloadItem* item = *iter;
1073 int item_id = item->GetId();
1074 CHECK(item_id >= 0);
1075 DCHECK(current_map.find(item_id) == current_map.end());
1076 current_set.insert(item_id);
1077 current_map[item_id] = item;
1078 }
1079 DownloadIdSet new_set; // current_set - prev_set;
1080 std::set_difference(current_set.begin(), current_set.end(),
1081 prev_set.begin(), prev_set.end(),
1082 std::insert_iterator<DownloadIdSet>(
1083 new_set, new_set.begin()));
1084
1085 // For each download that was not in the old set of downloads but is in the
1086 // new set of downloads, fire an onCreated event, register as an Observer of
1087 // the item, store a json representation of the item so that we can easily
1088 // find changes in that json representation, and make an OnChangedStat.
1089 for (DownloadIdSet::const_iterator iter = new_set.begin();
1090 iter != new_set.end(); ++iter) {
1091 scoped_ptr<base::DictionaryValue> item(
1092 DownloadItemToJSON(current_map[*iter]));
1093 DispatchEvent(extension_event_names::kOnDownloadCreated, item->DeepCopy());
1094 DCHECK(item_jsons_.find(*iter) == item_jsons_.end());
1095 on_changed_stats_[*iter] = new OnChangedStat();
1096 current_map[*iter]->AddObserver(this);
1097 item_jsons_[*iter] = item.release();
1098 }
1099 downloads_.swap(current_map);
1100
1101 // Dispatching onErased is handled in OnDownloadUpdated when an item
1102 // transitions to the REMOVING state.
1103 }
1104
1105 void ExtensionDownloadsEventRouter::ManagerGoingDown(
1106 DownloadManager* manager) {
1107 manager_->RemoveObserver(this);
1108 manager_ = NULL;
1109 }
1110
1111 void ExtensionDownloadsEventRouter::DispatchEvent(
1112 const char* event_name, base::Value* arg) {
1113 ListValue args;
1114 args.Append(arg);
1115 std::string json_args;
1116 base::JSONWriter::Write(&args, &json_args);
1117 profile_->GetExtensionEventRouter()->DispatchEventToRenderers(
1118 event_name,
1119 json_args,
1120 profile_,
1121 GURL());
1122 }
OLDNEW
« no previous file with comments | « chrome/browser/download/download_extension_api.h ('k') | chrome/browser/download/download_extension_apitest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698