summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbenjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-28 18:36:09 +0000
committerbenjhayden@chromium.org <benjhayden@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-28 18:36:09 +0000
commitf1d784d6938b8fe8e0d257e41b26341992c2552c (patch)
treee8a57be7f78ffa26e886c5814d989d0a8da3893d
parent3c919d6ac5b262904d6598fc2418d7c0358aad57 (diff)
downloadchromium_src-f1d784d6938b8fe8e0d257e41b26341992c2552c.zip
chromium_src-f1d784d6938b8fe8e0d257e41b26341992c2552c.tar.gz
chromium_src-f1d784d6938b8fe8e0d257e41b26341992c2552c.tar.bz2
A few minor changes to the chrome.downloads extension API
Reviewers: jhawkins: webui Done: asanka: */download/* jam: content/public/* asargent: */extensions/* Replaced the "Invalid operation" error message with more specific and helpful messages Add DownloadItem.referrer Add DownloadItem.estimatedEndTime Add DownloadItem.canResume Change DownloadQuery.limit to default to 1000 Change 'conflict_action' to 'conflictAction'. Change DownloadQuery.orderBy and DownloadQuery.query to arrays of strings Remove DownloadItem.dangerAccepted in favor of DownloadItem.danger == 'accepted' Add showDefaultFolder(). Disallow access to packaged_apps. download() now updates the Download.Sources histogram. http://crbug.com/240322 Document using startedAfter and limit to page through search() results. DownloadItem.error is now a string enum instead of mysterious numbers. Add downloads.removeFile(id) and DownloadItem::DeleteFile() so that the file may be deleted separately from the history entry. Calling open() for dangerous downloads has always been guaranteed to return kInvalidOperation because unaccepted dangerous downloads cannot transition to state='complete', and open() returns kInvalidOperation for incomplete items. We can wait until after launch to figure out whether there needs to be some mechanism to allow extensions to override the request header blacklist. This seems like an edge case for which we may never receive feature requests. Staged docs preview: http://basho.cam.corp.google.com:8000/extensions/downloads.html#type-DownloadItem BUG=240322 R=asanka@chromium.org, asargent@chromium.org, jam@chromium.org Review URL: https://codereview.chromium.org/16924017 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214133 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/download/download_danger_prompt.cc74
-rw-r--r--chrome/browser/download/download_danger_prompt.h7
-rw-r--r--chrome/browser/download/download_danger_prompt_browsertest.cc21
-rw-r--r--chrome/browser/download/download_query.cc58
-rw-r--r--chrome/browser/download/download_query.h2
-rw-r--r--chrome/browser/download/download_query_unittest.cc32
-rw-r--r--chrome/browser/download/download_util.h3
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api.cc489
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api.h74
-rw-r--r--chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc (renamed from chrome/browser/extensions/api/downloads/downloads_api_unittest.cc)323
-rw-r--r--chrome/browser/extensions/extension_function_histogram_value.h2
-rw-r--r--chrome/browser/ui/webui/downloads_dom_handler.cc10
-rw-r--r--chrome/browser/ui/webui/downloads_dom_handler.h3
-rw-r--r--chrome/chrome_tests.gypi2
-rw-r--r--chrome/common/extensions/api/_permission_features.json2
-rw-r--r--chrome/common/extensions/api/downloads.idl185
-rw-r--r--chrome/renderer/resources/extensions/downloads_custom_bindings.js29
-rw-r--r--content/browser/download/download_item_impl.cc14
-rw-r--r--content/browser/download/download_item_impl.h1
-rw-r--r--content/public/browser/download_item.h6
-rw-r--r--content/public/test/mock_download_item.h1
21 files changed, 862 insertions, 476 deletions
diff --git a/chrome/browser/download/download_danger_prompt.cc b/chrome/browser/download/download_danger_prompt.cc
index 95ec322..09c1624 100644
--- a/chrome/browser/download/download_danger_prompt.cc
+++ b/chrome/browser/download/download_danger_prompt.cc
@@ -5,6 +5,7 @@
#include "chrome/browser/download/download_danger_prompt.h"
#include "base/bind.h"
+#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/download/chrome_download_manager_delegate.h"
#include "chrome/browser/ui/tab_modal_confirm_dialog.h"
#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
@@ -23,8 +24,7 @@ class DownloadDangerPromptImpl
public:
DownloadDangerPromptImpl(content::DownloadItem* item,
bool show_context,
- const base::Closure& accepted,
- const base::Closure& canceled);
+ const OnDone& done);
virtual ~DownloadDangerPromptImpl();
// DownloadDangerPrompt
@@ -33,7 +33,6 @@ class DownloadDangerPromptImpl
private:
// content::DownloadItem::Observer
virtual void OnDownloadUpdated(content::DownloadItem* download) OVERRIDE;
- virtual void OnDownloadOpened(content::DownloadItem* download) OVERRIDE;
// TabModalConfirmDialogDelegate
virtual string16 GetTitle() OVERRIDE;
@@ -42,20 +41,11 @@ class DownloadDangerPromptImpl
virtual void OnAccepted() OVERRIDE;
virtual void OnCanceled() OVERRIDE;
- // Runs |callback|. PrepareToClose() is called beforehand. Doing so prevents
- // this object from responding to state changes in |download_| that might
- // result from invoking the callback. |callback| must refer to either
- // |accepted_| or |canceled_|.
- void RunCallback(const base::Closure& callback);
-
- // Resets |accepted_|, |canceled_| and removes the observer from |download_|,
- // in preparation for closing the prompt.
- void PrepareToClose();
+ void RunDone(Action action);
content::DownloadItem* download_;
bool show_context_;
- base::Closure accepted_;
- base::Closure canceled_;
+ OnDone done_;
DISALLOW_COPY_AND_ASSIGN(DownloadDangerPromptImpl);
};
@@ -63,29 +53,29 @@ class DownloadDangerPromptImpl
DownloadDangerPromptImpl::DownloadDangerPromptImpl(
content::DownloadItem* download,
bool show_context,
- const base::Closure& accepted,
- const base::Closure& canceled)
+ const OnDone& done)
: download_(download),
show_context_(show_context),
- accepted_(accepted),
- canceled_(canceled) {
- DCHECK(!accepted_.is_null());
- // canceled_ is allowed to be null.
- DCHECK(download_);
+ done_(done) {
+ DCHECK(!done_.is_null());
download_->AddObserver(this);
}
DownloadDangerPromptImpl::~DownloadDangerPromptImpl() {
// |this| might be deleted without invoking any callbacks. E.g. pressing Esc
// on GTK or if the user navigates away from the page showing the prompt.
- PrepareToClose();
+ RunDone(DISMISS);
}
void DownloadDangerPromptImpl::InvokeActionForTesting(Action action) {
- if (action == ACCEPT)
- Accept();
- else
- Cancel();
+ switch (action) {
+ case ACCEPT: Accept(); break;
+ case CANCEL: Cancel(); break;
+ case DISMISS:
+ RunDone(DISMISS);
+ Cancel();
+ break;
+ }
}
void DownloadDangerPromptImpl::OnDownloadUpdated(
@@ -93,12 +83,10 @@ void DownloadDangerPromptImpl::OnDownloadUpdated(
// If the download is nolonger dangerous (accepted externally) or the download
// is in a terminal state, then the download danger prompt is no longer
// necessary.
- if (!download->IsDangerous() || download->IsDone())
+ if (!download->IsDangerous() || download->IsDone()) {
+ RunDone(DISMISS);
Cancel();
-}
-
-void DownloadDangerPromptImpl::OnDownloadOpened(
- content::DownloadItem* download) {
+ }
}
string16 DownloadDangerPromptImpl::GetTitle() {
@@ -144,30 +132,25 @@ string16 DownloadDangerPromptImpl::GetAcceptButtonTitle() {
}
void DownloadDangerPromptImpl::OnAccepted() {
- RunCallback(accepted_);
+ RunDone(ACCEPT);
}
void DownloadDangerPromptImpl::OnCanceled() {
- RunCallback(canceled_);
+ RunDone(CANCEL);
}
-void DownloadDangerPromptImpl::RunCallback(const base::Closure& callback) {
+void DownloadDangerPromptImpl::RunDone(Action action) {
// Invoking the callback can cause the download item state to change or cause
// the constrained window to close, and |callback| refers to a member
// variable.
- base::Closure callback_copy = callback;
- PrepareToClose();
- if (!callback_copy.is_null())
- callback_copy.Run();
-}
-
-void DownloadDangerPromptImpl::PrepareToClose() {
- accepted_.Reset();
- canceled_.Reset();
+ OnDone done = done_;
+ done_.Reset();
if (download_ != NULL) {
download_->RemoveObserver(this);
download_ = NULL;
}
+ if (!done.is_null())
+ done.Run(action);
}
} // namespace
@@ -177,10 +160,9 @@ DownloadDangerPrompt* DownloadDangerPrompt::Create(
content::DownloadItem* item,
content::WebContents* web_contents,
bool show_context,
- const base::Closure& accepted,
- const base::Closure& canceled) {
+ const OnDone& done) {
DownloadDangerPromptImpl* prompt = new DownloadDangerPromptImpl(
- item, show_context, accepted, canceled);
+ item, show_context, done);
// |prompt| will be deleted when the dialog is done.
TabModalConfirmDialog::Create(prompt, web_contents);
return prompt;
diff --git a/chrome/browser/download/download_danger_prompt.h b/chrome/browser/download/download_danger_prompt.h
index 2ab5bb0..8c2407f 100644
--- a/chrome/browser/download/download_danger_prompt.h
+++ b/chrome/browser/download/download_danger_prompt.h
@@ -24,8 +24,10 @@ class DownloadDangerPrompt {
// Actions resulting from showing the danger prompt.
enum Action {
ACCEPT,
- CANCEL
+ CANCEL,
+ DISMISS,
};
+ typedef base::Callback<void(Action)> OnDone;
// Return a new self-deleting DownloadDangerPrompt. |accepted| or |canceled|
// will be run when the the respective action is invoked. |canceled| may also
@@ -39,8 +41,7 @@ class DownloadDangerPrompt {
content::DownloadItem* item,
content::WebContents* web_contents,
bool show_context,
- const base::Closure& accepted,
- const base::Closure& canceled);
+ const OnDone& done);
protected:
friend class DownloadDangerPromptTest;
diff --git a/chrome/browser/download/download_danger_prompt_browsertest.cc b/chrome/browser/download/download_danger_prompt_browsertest.cc
index 5a7c9e8..f203b71 100644
--- a/chrome/browser/download/download_danger_prompt_browsertest.cc
+++ b/chrome/browser/download/download_danger_prompt_browsertest.cc
@@ -86,10 +86,7 @@ class DownloadDangerPromptTest : public InProcessBrowserTest {
&download_,
browser()->tab_strip_model()->GetActiveWebContents(),
false,
- base::Bind(&DownloadDangerPromptTest::PromptCallback, this,
- DownloadDangerPrompt::ACCEPT),
- base::Bind(&DownloadDangerPromptTest::PromptCallback, this,
- DownloadDangerPrompt::CANCEL));
+ base::Bind(&DownloadDangerPromptTest::PromptCallback, this));
content::RunAllPendingInMessageLoop();
}
@@ -112,26 +109,26 @@ class DownloadDangerPromptTest : public InProcessBrowserTest {
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, TestAll) {
OpenNewTab();
- // The Accept action should cause the accept callback to be invoked.
+ // Clicking the Accept button should invoke the ACCEPT action.
SetUpExpectations(DownloadDangerPrompt::ACCEPT);
SimulatePromptAction(DownloadDangerPrompt::ACCEPT);
VerifyExpectations();
- // The Discard action should cause the discard callback to be invoked.
+ // Clicking the Cancel button should invoke the CANCEL action.
SetUpExpectations(DownloadDangerPrompt::CANCEL);
SimulatePromptAction(DownloadDangerPrompt::CANCEL);
VerifyExpectations();
// If the download is no longer dangerous (because it was accepted), the
- // dialog should dismiss itself.
- SetUpExpectations(DownloadDangerPrompt::CANCEL);
+ // dialog should DISMISS itself.
+ SetUpExpectations(DownloadDangerPrompt::DISMISS);
EXPECT_CALL(download(), IsDangerous()).WillOnce(Return(false));
download_observer()->OnDownloadUpdated(&download());
VerifyExpectations();
- // If the download is in a terminal state then the dialog should dismiss
+ // If the download is in a terminal state then the dialog should DISMISS
// itself.
- SetUpExpectations(DownloadDangerPrompt::CANCEL);
+ SetUpExpectations(DownloadDangerPrompt::DISMISS);
EXPECT_CALL(download(), IsDangerous()).WillOnce(Return(true));
EXPECT_CALL(download(), IsDone()).WillOnce(Return(true));
download_observer()->OnDownloadUpdated(&download());
@@ -146,8 +143,10 @@ IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, TestAll) {
SimulatePromptAction(DownloadDangerPrompt::ACCEPT);
VerifyExpectations();
- // If the containing tab is closed, the dialog should be canceled.
+ // If the containing tab is closed, the dialog should DISMISS itself.
OpenNewTab();
+ // TODO(benjhayden):
+ // SetUpExpectations(DownloadDangerPrompt::DISMISS);
SetUpExpectations(DownloadDangerPrompt::CANCEL);
chrome::CloseTab(browser());
VerifyExpectations();
diff --git a/chrome/browser/download/download_query.cc b/chrome/browser/download/download_query.cc
index 14bc73e..57ea7f3 100644
--- a/chrome/browser/download/download_query.cc
+++ b/chrome/browser/download/download_query.cc
@@ -50,22 +50,30 @@ template<> bool GetAs(const base::Value& in, std::string* out) {
template<> bool GetAs(const base::Value& in, string16* out) {
return in.GetAsString(out);
}
+template<> bool GetAs(const base::Value& in, std::vector<string16>* out) {
+ out->clear();
+ const base::ListValue* list = NULL;
+ if (!in.GetAsList(&list))
+ return false;
+ for (size_t i = 0; i < list->GetSize(); ++i) {
+ string16 element;
+ if (!list->GetString(i, &element)) {
+ out->clear();
+ return false;
+ }
+ out->push_back(element);
+ }
+ return true;
+}
// The next several functions are helpers for making Callbacks that access
// DownloadItem fields.
-static bool MatchesQuery(const string16& query, const DownloadItem& item) {
- if (query.empty())
- return true;
-
- DCHECK_EQ(query, base::i18n::ToLower(query));
-
+static bool MatchesQuery(
+ const std::vector<string16>& query_terms,
+ const DownloadItem& item) {
+ DCHECK(!query_terms.empty());
string16 url_raw(UTF8ToUTF16(item.GetOriginalUrl().spec()));
- if (base::i18n::StringSearchIgnoringCaseAndAccents(
- query, url_raw, NULL, NULL)) {
- return true;
- }
-
string16 url_formatted = url_raw;
if (item.GetBrowserContext()) {
Profile* profile = Profile::FromBrowserContext(item.GetBrowserContext());
@@ -73,14 +81,21 @@ static bool MatchesQuery(const string16& query, const DownloadItem& item) {
item.GetOriginalUrl(),
profile->GetPrefs()->GetString(prefs::kAcceptLanguages));
}
- if (base::i18n::StringSearchIgnoringCaseAndAccents(
- query, url_formatted, NULL, NULL)) {
- return true;
- }
-
string16 path(item.GetTargetFilePath().LossyDisplayName());
- return base::i18n::StringSearchIgnoringCaseAndAccents(
- query, path, NULL, NULL);
+
+ for (std::vector<string16>::const_iterator it = query_terms.begin();
+ it != query_terms.end(); ++it) {
+ string16 term = base::i18n::ToLower(*it);
+ if (!base::i18n::StringSearchIgnoringCaseAndAccents(
+ term, url_raw, NULL, NULL) &&
+ !base::i18n::StringSearchIgnoringCaseAndAccents(
+ term, url_formatted, NULL, NULL) &&
+ !base::i18n::StringSearchIgnoringCaseAndAccents(
+ term, path, NULL, NULL)) {
+ return false;
+ }
+ }
+ return true;
}
static int64 GetStartTimeMsEpoch(const DownloadItem& item) {
@@ -268,9 +283,10 @@ bool DownloadQuery::AddFilter(DownloadQuery::FilterType type,
case FILTER_PAUSED:
return AddFilter(BuildFilter<bool>(value, EQ, &IsPaused));
case FILTER_QUERY: {
- string16 query;
- return GetAs(value, &query) &&
- AddFilter(base::Bind(&MatchesQuery, query));
+ std::vector<string16> query_terms;
+ return GetAs(value, &query_terms) &&
+ (query_terms.empty() ||
+ AddFilter(base::Bind(&MatchesQuery, query_terms)));
}
case FILTER_ENDED_AFTER:
return AddFilter(BuildFilter<std::string>(value, GT, &GetEndTime));
diff --git a/chrome/browser/download/download_query.h b/chrome/browser/download/download_query.h
index 2da7a1b..533265a 100644
--- a/chrome/browser/download/download_query.h
+++ b/chrome/browser/download/download_query.h
@@ -58,7 +58,7 @@ class DownloadQuery {
FILTER_FILENAME_REGEX, // string
FILTER_MIME, // string
FILTER_PAUSED, // bool
- FILTER_QUERY, // string
+ FILTER_QUERY, // vector<string16>
FILTER_STARTED_AFTER, // string
FILTER_STARTED_BEFORE, // string
FILTER_START_TIME, // string
diff --git a/chrome/browser/download/download_query_unittest.cc b/chrome/browser/download/download_query_unittest.cc
index 00b36ad..17bf351 100644
--- a/chrome/browser/download/download_query_unittest.cc
+++ b/chrome/browser/download/download_query_unittest.cc
@@ -128,6 +128,26 @@ template<> void DownloadQueryTest::AddFilter(
CHECK(query_.AddFilter(name, *value.get()));
}
+template<> void DownloadQueryTest::AddFilter(
+ DownloadQuery::FilterType name, std::vector<string16> cpp_value) {
+ scoped_ptr<base::ListValue> list(new base::ListValue());
+ for (std::vector<string16>::const_iterator it = cpp_value.begin();
+ it != cpp_value.end(); ++it) {
+ list->Append(Value::CreateStringValue(*it));
+ }
+ CHECK(query_.AddFilter(name, *list.get()));
+}
+
+template<> void DownloadQueryTest::AddFilter(
+ DownloadQuery::FilterType name, std::vector<std::string> cpp_value) {
+ scoped_ptr<base::ListValue> list(new base::ListValue());
+ for (std::vector<std::string>::const_iterator it = cpp_value.begin();
+ it != cpp_value.end(); ++it) {
+ list->Append(Value::CreateStringValue(*it));
+ }
+ CHECK(query_.AddFilter(name, *list.get()));
+}
+
#if defined(OS_WIN)
template<> void DownloadQueryTest::AddFilter(
DownloadQuery::FilterType name, std::wstring cpp_value) {
@@ -177,7 +197,9 @@ TEST_F(DownloadQueryTest, DownloadQueryTest_FilterGenericQueryFilename) {
GURL fail_url("http://example.com/fail");
EXPECT_CALL(mock(0), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url));
EXPECT_CALL(mock(1), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url));
- AddFilter(DownloadQuery::FILTER_QUERY, "query");
+ std::vector<std::string> query_terms;
+ query_terms.push_back("query");
+ AddFilter(DownloadQuery::FILTER_QUERY, query_terms);
ExpectStandardFilterResults();
}
@@ -196,7 +218,9 @@ TEST_F(DownloadQueryTest, DownloadQueryTest_FilterGenericQueryUrl) {
EXPECT_CALL(mock(0), GetOriginalUrl()).WillRepeatedly(ReturnRef(match_url));
GURL fail_url("http://example.com/fail");
EXPECT_CALL(mock(1), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url));
- AddFilter(DownloadQuery::FILTER_QUERY, "query");
+ std::vector<std::string> query_terms;
+ query_terms.push_back("query");
+ AddFilter(DownloadQuery::FILTER_QUERY, query_terms);
ExpectStandardFilterResults();
}
@@ -222,7 +246,9 @@ TEST_F(DownloadQueryTest, DownloadQueryTest_FilterGenericQueryFilenameI18N) {
GURL fail_url("http://example.com/fail");
EXPECT_CALL(mock(0), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url));
EXPECT_CALL(mock(1), GetOriginalUrl()).WillRepeatedly(ReturnRef(fail_url));
- AddFilter(DownloadQuery::FILTER_QUERY, kTestString);
+ std::vector<base::FilePath::StringType> query_terms;
+ query_terms.push_back(kTestString);
+ AddFilter(DownloadQuery::FILTER_QUERY, query_terms);
ExpectStandardFilterResults();
}
diff --git a/chrome/browser/download/download_util.h b/chrome/browser/download/download_util.h
index 7900868..e2aca39 100644
--- a/chrome/browser/download/download_util.h
+++ b/chrome/browser/download/download_util.h
@@ -186,6 +186,9 @@ enum ChromeDownloadSource {
// The download was initiated by the PDF plugin..
INITIATED_BY_PDF_SAVE,
+ // The download was initiated by chrome.downloads.download().
+ INITIATED_BY_EXTENSION,
+
CHROME_DOWNLOAD_SOURCE_LAST_ENTRY,
};
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index 7de85f4..0bfa228 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -14,6 +14,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
+#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_writer.h"
#include "base/lazy_instance.h"
@@ -30,9 +31,11 @@
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/download/download_danger_prompt.h"
#include "chrome/browser/download/download_file_icon_extractor.h"
+#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/download/download_query.h"
#include "chrome/browser/download/download_service.h"
#include "chrome/browser/download/download_service_factory.h"
+#include "chrome/browser/download/download_shelf.h"
#include "chrome/browser/download/download_util.h"
#include "chrome/browser/extensions/event_names.h"
#include "chrome/browser/extensions/event_router.h"
@@ -43,8 +46,10 @@
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/icon_loader.h"
#include "chrome/browser/icon_manager.h"
+#include "chrome/browser/platform_util.h"
#include "chrome/browser/renderer_host/chrome_render_message_filter.h"
#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
#include "chrome/common/cancelable_task_tracker.h"
#include "chrome/common/extensions/api/downloads.h"
#include "chrome/common/extensions/permissions/permissions_data.h"
@@ -75,24 +80,34 @@ using content::DownloadManager;
namespace download_extension_errors {
-// Error messages
-const char kGenericError[] = "I'm afraid I can't do that";
-const char kIconNotFoundError[] = "Icon not found";
-const char kInvalidDangerTypeError[] = "Invalid danger type";
-const char kInvalidFilenameError[] = "Invalid filename";
-const char kInvalidFilterError[] = "Invalid query filter";
-const char kInvalidOperationError[] = "Invalid operation";
-const char kInvalidOrderByError[] = "Invalid orderBy field";
+const char kEmptyFile[] = "Filename not yet determined";
+const char kFileAlreadyDeleted[] = "Download file already deleted";
+const char kIconNotFound[] = "Icon not found";
+const char kInvalidDangerType[] = "Invalid danger type";
+const char kInvalidFilename[] = "Invalid filename";
+const char kInvalidFilter[] = "Invalid query filter";
+const char kInvalidHeader[] = "Invalid request header";
+const char kInvalidId[] = "Invalid downloadId";
+const char kInvalidOrderBy[] = "Invalid orderBy field";
const char kInvalidQueryLimit[] = "Invalid query limit";
-const char kInvalidStateError[] = "Invalid state";
-const char kInvalidURLError[] = "Invalid URL";
-const char kNotImplementedError[] = "NotImplemented";
-const char kTooManyListenersError[] = "Each extension may have at most one "
+const char kInvalidState[] = "Invalid state";
+const char kInvalidURL[] = "Invalid URL";
+const char kInvisibleContext[] = "Javascript execution context is not visible "
+ "(tab, window, popup bubble)";
+const char kNotComplete[] = "Download must be complete";
+const char kNotDangerous[] = "Download must be dangerous";
+const char kNotInProgress[] = "Download must be in progress";
+const char kNotResumable[] = "DownloadItem.canResume must be true";
+const char kOpenPermission[] = "The \"downloads.open\" permission is required";
+const char kTooManyListeners[] = "Each extension may have at most one "
"onDeterminingFilename listener between all of its renderer execution "
"contexts.";
+const char kUnexpectedDeterminer[] = "Unexpected determineFilename call";
} // namespace download_extension_errors
+namespace errors = download_extension_errors;
+
namespace {
// Default icon size for getFileIcon() in pixels.
@@ -100,29 +115,31 @@ const int kDefaultIconSize = 32;
// Parameter keys
const char kBytesReceivedKey[] = "bytesReceived";
-const char kDangerAcceptedKey[] = "dangerAccepted";
+const char kCanResumeKey[] = "canResume";
+const char kDangerAccepted[] = "accepted";
const char kDangerContent[] = "content";
const char kDangerFile[] = "file";
+const char kDangerHost[] = "host";
const char kDangerKey[] = "danger";
const char kDangerSafe[] = "safe";
const char kDangerUncommon[] = "uncommon";
const char kDangerUnwanted[] = "unwanted";
-const char kDangerAccepted[] = "accepted";
-const char kDangerHost[] = "host";
const char kDangerUrl[] = "url";
const char kEndTimeKey[] = "endTime";
const char kEndedAfterKey[] = "endedAfter";
const char kEndedBeforeKey[] = "endedBefore";
const char kErrorKey[] = "error";
+const char kEstimatedEndTimeKey[] = "estimatedEndTime";
const char kExistsKey[] = "exists";
const char kFileSizeKey[] = "fileSize";
const char kFilenameKey[] = "filename";
const char kFilenameRegexKey[] = "filenameRegex";
const char kIdKey[] = "id";
-const char kIncognito[] = "incognito";
+const char kIncognitoKey[] = "incognito";
const char kMimeKey[] = "mime";
const char kPausedKey[] = "paused";
const char kQueryKey[] = "query";
+const char kReferrerUrlKey[] = "referrer";
const char kStartTimeKey[] = "startTime";
const char kStartedAfterKey[] = "startedAfter";
const char kStartedBeforeKey[] = "startedBefore";
@@ -216,30 +233,34 @@ scoped_ptr<base::DictionaryValue> DownloadItemToJSON(
json->SetInteger(kIdKey, download_item->GetId());
const GURL& url = download_item->GetOriginalUrl();
json->SetString(kUrlKey, (url.is_valid() ? url.spec() : std::string()));
+ const GURL& referrer = download_item->GetReferrerUrl();
+ json->SetString(kReferrerUrlKey, (referrer.is_valid() ? referrer.spec()
+ : std::string()));
json->SetString(kFilenameKey,
download_item->GetTargetFilePath().LossyDisplayName());
json->SetString(kDangerKey, DangerString(download_item->GetDangerType()));
- if (download_item->GetDangerType() !=
- content::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS)
- json->SetBoolean(kDangerAcceptedKey,
- download_item->GetDangerType() ==
- content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED);
json->SetString(kStateKey, StateString(download_item->GetState()));
+ json->SetBoolean(kCanResumeKey, download_item->CanResume());
json->SetBoolean(kPausedKey, download_item->IsPaused());
json->SetString(kMimeKey, download_item->GetMimeType());
json->SetString(kStartTimeKey, TimeToISO8601(download_item->GetStartTime()));
json->SetInteger(kBytesReceivedKey, download_item->GetReceivedBytes());
json->SetInteger(kTotalBytesKey, download_item->GetTotalBytes());
- json->SetBoolean(kIncognito, incognito);
+ json->SetBoolean(kIncognitoKey, incognito);
if (download_item->GetState() == DownloadItem::INTERRUPTED) {
- json->SetInteger(kErrorKey, static_cast<int>(
+ json->SetString(kErrorKey, content::InterruptReasonDebugString(
download_item->GetLastReason()));
} else if (download_item->GetState() == DownloadItem::CANCELLED) {
- json->SetInteger(kErrorKey, static_cast<int>(
+ json->SetString(kErrorKey, content::InterruptReasonDebugString(
content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED));
}
if (!download_item->GetEndTime().is_null())
json->SetString(kEndTimeKey, TimeToISO8601(download_item->GetEndTime()));
+ base::TimeDelta time_remaining;
+ if (download_item->TimeRemaining(&time_remaining)) {
+ base::Time now = base::Time::Now();
+ json->SetString(kEstimatedEndTimeKey, TimeToISO8601(now + time_remaining));
+ }
// TODO(benjhayden): Implement fileSize.
json->SetInteger(kFileSizeKey, download_item->GetTotalBytes());
return scoped_ptr<base::DictionaryValue>(json);
@@ -300,7 +321,6 @@ typedef base::hash_map<std::string, DownloadQuery::FilterType> FilterTypeMap;
void InitFilterTypeMap(FilterTypeMap& filter_types) {
filter_types[kBytesReceivedKey] = DownloadQuery::FILTER_BYTES_RECEIVED;
- filter_types[kDangerAcceptedKey] = DownloadQuery::FILTER_DANGER_ACCEPTED;
filter_types[kExistsKey] = DownloadQuery::FILTER_EXISTS;
filter_types[kFilenameKey] = DownloadQuery::FILTER_FILENAME;
filter_types[kFilenameRegexKey] = DownloadQuery::FILTER_FILENAME_REGEX;
@@ -326,7 +346,6 @@ typedef base::hash_map<std::string, DownloadQuery::SortType> SortTypeMap;
void InitSortTypeMap(SortTypeMap& sorter_types) {
sorter_types[kBytesReceivedKey] = DownloadQuery::SORT_BYTES_RECEIVED;
sorter_types[kDangerKey] = DownloadQuery::SORT_DANGER;
- sorter_types[kDangerAcceptedKey] = DownloadQuery::SORT_DANGER_ACCEPTED;
sorter_types[kEndTimeKey] = DownloadQuery::SORT_END_TIME;
sorter_types[kExistsKey] = DownloadQuery::SORT_EXISTS;
sorter_types[kFilenameKey] = DownloadQuery::SORT_FILENAME;
@@ -372,16 +391,6 @@ DownloadItem* GetDownload(Profile* profile, bool include_incognito, int id) {
return download_item;
}
-DownloadItem* GetDownloadIfInProgress(
- Profile* profile,
- bool include_incognito,
- int id) {
- DownloadItem* download_item = GetDownload(profile, include_incognito, id);
- if (download_item && (download_item->GetState() == DownloadItem::IN_PROGRESS))
- return download_item;
- return NULL;
-}
-
enum DownloadsFunctionName {
DOWNLOADS_FUNCTION_DOWNLOAD = 0,
DOWNLOADS_FUNCTION_SEARCH = 1,
@@ -395,6 +404,8 @@ enum DownloadsFunctionName {
DOWNLOADS_FUNCTION_DRAG = 9,
DOWNLOADS_FUNCTION_GET_FILE_ICON = 10,
DOWNLOADS_FUNCTION_OPEN = 11,
+ DOWNLOADS_FUNCTION_REMOVE_FILE = 12,
+ DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER = 13,
// Insert new values here, not at the beginning.
DOWNLOADS_FUNCTION_LAST
};
@@ -406,7 +417,9 @@ void RecordApiFunctions(DownloadsFunctionName function) {
}
void CompileDownloadQueryOrderBy(
- const std::string& order_by_str, std::string* error, DownloadQuery* query) {
+ const std::vector<std::string>& order_by_strs,
+ std::string* error,
+ DownloadQuery* query) {
// TODO(benjhayden): Consider switching from LazyInstance to explicit string
// comparisons.
static base::LazyInstance<SortTypeMap> sorter_types =
@@ -414,8 +427,6 @@ void CompileDownloadQueryOrderBy(
if (sorter_types.Get().size() == 0)
InitSortTypeMap(sorter_types.Get());
- std::vector<std::string> order_by_strs;
- base::SplitString(order_by_str, ' ', &order_by_strs);
for (std::vector<std::string>::const_iterator iter = order_by_strs.begin();
iter != order_by_strs.end(); ++iter) {
std::string term_str = *iter;
@@ -429,7 +440,7 @@ void CompileDownloadQueryOrderBy(
SortTypeMap::const_iterator sorter_type =
sorter_types.Get().find(term_str);
if (sorter_type == sorter_types.Get().end()) {
- *error = download_extension_errors::kInvalidOrderByError;
+ *error = errors::kInvalidOrderBy;
return;
}
query->AddSorter(sorter_type->second, direction);
@@ -451,19 +462,24 @@ void RunDownloadQuery(
DownloadQuery query_out;
+ size_t limit = 1000;
if (query_in.limit.get()) {
if (*query_in.limit.get() < 0) {
- *error = download_extension_errors::kInvalidQueryLimit;
+ *error = errors::kInvalidQueryLimit;
return;
}
- query_out.Limit(*query_in.limit.get());
+ limit = *query_in.limit.get();
}
+ if (limit > 0) {
+ query_out.Limit(limit);
+ }
+
std::string state_string =
extensions::api::downloads::ToString(query_in.state);
if (!state_string.empty()) {
DownloadItem::DownloadState state = StateEnumFromString(state_string);
if (state == DownloadItem::MAX_DOWNLOAD_STATE) {
- *error = download_extension_errors::kInvalidStateError;
+ *error = errors::kInvalidState;
return;
}
query_out.AddFilter(state);
@@ -474,7 +490,7 @@ void RunDownloadQuery(
content::DownloadDangerType danger_type = DangerEnumFromString(
danger_string);
if (danger_type == content::DOWNLOAD_DANGER_TYPE_MAX) {
- *error = download_extension_errors::kInvalidDangerTypeError;
+ *error = errors::kInvalidDangerType;
return;
}
query_out.AddFilter(danger_type);
@@ -492,7 +508,7 @@ void RunDownloadQuery(
filter_types.Get().find(query_json_field.key());
if (filter_type != filter_types.Get().end()) {
if (!query_out.AddFilter(filter_type->second, query_json_field.value())) {
- *error = download_extension_errors::kInvalidFilterError;
+ *error = errors::kInvalidFilter;
return;
}
}
@@ -514,6 +530,21 @@ void RunDownloadQuery(
query_out.Search(all_items.begin(), all_items.end(), results);
}
+DownloadPathReservationTracker::FilenameConflictAction ConvertConflictAction(
+ extensions::api::downloads::FilenameConflictAction action) {
+ switch (action) {
+ case extensions::api::downloads::FILENAME_CONFLICT_ACTION_NONE:
+ case extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY:
+ return DownloadPathReservationTracker::UNIQUIFY;
+ case extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE:
+ return DownloadPathReservationTracker::OVERWRITE;
+ case extensions::api::downloads::FILENAME_CONFLICT_ACTION_PROMPT:
+ return DownloadPathReservationTracker::PROMPT;
+ }
+ NOTREACHED();
+ return DownloadPathReservationTracker::UNIQUIFY;
+}
+
class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
public:
static ExtensionDownloadsEventRouterData* Get(DownloadItem* download_item) {
@@ -532,6 +563,8 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
: updated_(0),
changed_fired_(0),
json_(json_item.Pass()),
+ creator_conflict_action_(
+ extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY),
determined_conflict_action_(
extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY) {
download_item->SetUserData(kKey, this);
@@ -557,6 +590,10 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
const ExtensionDownloadsEventRouter::FilenameChangedCallback& change) {
filename_no_change_ = no_change;
filename_change_ = change;
+ determined_filename_ = creator_suggested_filename_;
+ determined_conflict_action_ = creator_conflict_action_;
+ // determiner_.install_time should default to 0 so that creator suggestions
+ // should be lower priority than any actual onDeterminingFilename listeners.
}
void ClearPendingDeterminers() {
@@ -604,6 +641,28 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
return false;
}
+ void CreatorSuggestedFilename(
+ const base::FilePath& filename,
+ extensions::api::downloads::FilenameConflictAction conflict_action) {
+ creator_suggested_filename_ = filename;
+ creator_conflict_action_ = conflict_action;
+ }
+
+ base::FilePath creator_suggested_filename() const {
+ return creator_suggested_filename_;
+ }
+
+ extensions::api::downloads::FilenameConflictAction
+ creator_conflict_action() const {
+ return creator_conflict_action_;
+ }
+
+ void ResetCreatorSuggestion() {
+ creator_suggested_filename_.clear();
+ creator_conflict_action_ =
+ extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY;
+ }
+
// Returns false if this |extension_id| was not expected or if this
// |extension_id| has already reported. The caller is responsible for
// validating |filename|.
@@ -665,22 +724,15 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
filename_no_change_.Run();
} else {
if (!filename_change_.is_null()) {
- DownloadPathReservationTracker::FilenameConflictAction conflict_action =
- DownloadPathReservationTracker::UNIQUIFY;
- if (determined_conflict_action_ ==
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE)
- conflict_action = DownloadPathReservationTracker::OVERWRITE;
- if (determined_conflict_action_ ==
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_PROMPT)
- conflict_action = DownloadPathReservationTracker::PROMPT;
- filename_change_.Run(determined_filename_, conflict_action);
+ filename_change_.Run(determined_filename_, ConvertConflictAction(
+ determined_conflict_action_));
}
}
// Don't clear determiners_ immediately in case there's a second listener
// for one of the extensions, so that DetermineFilename can return
- // kTooManyListenersError. After a few seconds, DetermineFilename will
- // return kInvalidOperationError instead of kTooManyListenersError so that
- // determiners_ doesn't keep hogging memory.
+ // kTooManyListeners. After a few seconds, DetermineFilename will return
+ // kUnexpectedDeterminer instead of kTooManyListeners so that determiners_
+ // doesn't keep hogging memory.
weak_ptr_factory_.reset(
new base::WeakPtrFactory<ExtensionDownloadsEventRouterData>(this));
base::MessageLoopForUI::current()->PostDelayedTask(
@@ -699,6 +751,9 @@ class ExtensionDownloadsEventRouterData : public base::SupportsUserData::Data {
DeterminerInfoVector determiners_;
+ base::FilePath creator_suggested_filename_;
+ extensions::api::downloads::FilenameConflictAction
+ creator_conflict_action_;
base::FilePath determined_filename_;
extensions::api::downloads::FilenameConflictAction
determined_conflict_action_;
@@ -796,6 +851,35 @@ void OnDeterminingFilenameWillDispatchCallback(
data->AddPendingDeterminer(extension->id(), installed);
}
+bool Fault(bool error,
+ const char* message_in,
+ std::string* message_out) {
+ if (!error)
+ return false;
+ *message_out = message_in;
+ return true;
+}
+
+bool InvalidId(DownloadItem* valid_item, std::string* message_out) {
+ return Fault(!valid_item, errors::kInvalidId, message_out);
+}
+
+bool IsDownloadDeltaField(const std::string& field) {
+ return ((field == kUrlKey) ||
+ (field == kFilenameKey) ||
+ (field == kDangerKey) ||
+ (field == kMimeKey) ||
+ (field == kStartTimeKey) ||
+ (field == kEndTimeKey) ||
+ (field == kStateKey) ||
+ (field == kCanResumeKey) ||
+ (field == kPausedKey) ||
+ (field == kErrorKey) ||
+ (field == kTotalBytesKey) ||
+ (field == kFileSizeKey) ||
+ (field == kExistsKey));
+}
+
} // namespace
DownloadsDownloadFunction::DownloadsDownloadFunction() {}
@@ -808,10 +892,8 @@ bool DownloadsDownloadFunction::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(params.get());
const extensions::api::downloads::DownloadOptions& options = params->options;
GURL download_url(options.url);
- if (!download_url.is_valid()) {
- error_ = download_extension_errors::kInvalidURLError;
+ if (Fault(!download_url.is_valid(), errors::kInvalidURL, &error_))
return false;
- }
Profile* current_profile = profile();
if (include_incognito() && profile()->HasOffTheRecordProfile())
@@ -824,28 +906,24 @@ bool DownloadsDownloadFunction::RunImpl() {
render_view_host()->GetRoutingID(),
current_profile->GetResourceContext()));
+ base::FilePath creator_suggested_filename;
if (options.filename.get()) {
- // TODO(benjhayden): Make json_schema_compiler generate string16s instead of
- // std::strings. Can't get filename16 from options.ToValue() because that
- // converts it from std::string.
+#if defined(OS_WIN)
+ // Can't get filename16 from options.ToValue() because that converts it from
+ // std::string.
base::DictionaryValue* options_value = NULL;
EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &options_value));
- string16 filename16;
+ base::string16 filename16;
EXTENSION_FUNCTION_VALIDATE(options_value->GetString(
kFilenameKey, &filename16));
-#if defined(OS_WIN)
- base::FilePath file_path(filename16);
+ creator_suggested_filename = base::FilePath(filename16);
#elif defined(OS_POSIX)
- base::FilePath file_path(*options.filename.get());
+ creator_suggested_filename = base::FilePath(*options.filename.get());
#endif
- if (!net::IsSafePortableBasename(file_path) ||
- (file_path.DirName().value() != base::FilePath::kCurrentDirectory)) {
- error_ = download_extension_errors::kInvalidFilenameError;
+ if (!net::IsSafePortableRelativePath(creator_suggested_filename)) {
+ error_ = errors::kInvalidFilename;
return false;
}
- // TODO(benjhayden) Ensure that this filename is interpreted as a path
- // relative to the default downloads directory without allowing '..'.
- download_params->set_suggested_name(filename16);
}
if (options.save_as.get())
@@ -859,7 +937,7 @@ bool DownloadsDownloadFunction::RunImpl() {
++iter) {
const HeaderNameValuePair& name_value = **iter;
if (!net::HttpUtil::IsSafeHeader(name_value.name)) {
- error_ = download_extension_errors::kGenericError;
+ error_ = errors::kInvalidHeader;
return false;
}
download_params->add_request_header(name_value.name, name_value.value);
@@ -873,24 +951,40 @@ bool DownloadsDownloadFunction::RunImpl() {
if (options.body.get())
download_params->set_post_body(*options.body.get());
download_params->set_callback(base::Bind(
- &DownloadsDownloadFunction::OnStarted, this));
+ &DownloadsDownloadFunction::OnStarted, this,
+ creator_suggested_filename, options.conflict_action));
// Prevent login prompts for 401/407 responses.
download_params->set_load_flags(net::LOAD_DO_NOT_PROMPT_FOR_LOGIN);
DownloadManager* manager = BrowserContext::GetDownloadManager(
current_profile);
manager->DownloadUrl(download_params.Pass());
+ download_util::RecordDownloadSource(download_util::INITIATED_BY_EXTENSION);
RecordApiFunctions(DOWNLOADS_FUNCTION_DOWNLOAD);
return true;
}
void DownloadsDownloadFunction::OnStarted(
- DownloadItem* item, net::Error error) {
+ const base::FilePath& creator_suggested_filename,
+ extensions::api::downloads::FilenameConflictAction creator_conflict_action,
+ DownloadItem* item,
+ net::Error error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
VLOG(1) << __FUNCTION__ << " " << item << " " << error;
if (item) {
DCHECK_EQ(net::OK, error);
SetResult(base::Value::CreateIntegerValue(item->GetId()));
+ if (!creator_suggested_filename.empty()) {
+ ExtensionDownloadsEventRouterData* data =
+ ExtensionDownloadsEventRouterData::Get(item);
+ if (!data) {
+ data = new ExtensionDownloadsEventRouterData(
+ item,
+ scoped_ptr<base::DictionaryValue>(new base::DictionaryValue()));
+ }
+ data->CreatorSuggestedFilename(
+ creator_suggested_filename, creator_conflict_action);
+ }
} else {
DCHECK_NE(net::OK, error);
error_ = net::ErrorToString(error);
@@ -944,20 +1038,17 @@ bool DownloadsPauseFunction::RunImpl() {
scoped_ptr<extensions::api::downloads::Pause::Params> params(
extensions::api::downloads::Pause::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
- DownloadItem* download_item = GetDownloadIfInProgress(
+ DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
- if (download_item == NULL) {
- // This could be due to an invalid download ID, or it could be due to the
- // download not being currently active.
- error_ = download_extension_errors::kInvalidOperationError;
- } else {
- // If the item is already paused, this is a no-op and the
- // operation will silently succeed.
- download_item->Pause();
- }
- if (error_.empty())
- RecordApiFunctions(DOWNLOADS_FUNCTION_PAUSE);
- return error_.empty();
+ if (InvalidId(download_item, &error_) ||
+ Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
+ errors::kNotInProgress, &error_))
+ return false;
+ // If the item is already paused, this is a no-op and the operation will
+ // silently succeed.
+ download_item->Pause();
+ RecordApiFunctions(DOWNLOADS_FUNCTION_PAUSE);
+ return true;
}
DownloadsResumeFunction::DownloadsResumeFunction() {}
@@ -968,20 +1059,17 @@ bool DownloadsResumeFunction::RunImpl() {
scoped_ptr<extensions::api::downloads::Resume::Params> params(
extensions::api::downloads::Resume::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
- DownloadItem* download_item = GetDownloadIfInProgress(
+ DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
- if (download_item == NULL) {
- // This could be due to an invalid download ID, or it could be due to the
- // download not being currently active.
- error_ = download_extension_errors::kInvalidOperationError;
- } else {
- // Note that if the item isn't paused, this will be a no-op, and
- // the extension call will seem successful.
- download_item->Resume();
- }
- if (error_.empty())
- RecordApiFunctions(DOWNLOADS_FUNCTION_RESUME);
- return error_.empty();
+ if (InvalidId(download_item, &error_) ||
+ Fault(download_item->IsPaused() && !download_item->CanResume(),
+ errors::kNotResumable, &error_))
+ return false;
+ // Note that if the item isn't paused, this will be a no-op, and the extension
+ // call will seem successful.
+ download_item->Resume();
+ RecordApiFunctions(DOWNLOADS_FUNCTION_RESUME);
+ return true;
}
DownloadsCancelFunction::DownloadsCancelFunction() {}
@@ -992,9 +1080,10 @@ bool DownloadsCancelFunction::RunImpl() {
scoped_ptr<extensions::api::downloads::Resume::Params> params(
extensions::api::downloads::Resume::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
- DownloadItem* download_item = GetDownloadIfInProgress(
+ DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
- if (download_item != NULL)
+ if (download_item &&
+ (download_item->GetState() == DownloadItem::IN_PROGRESS))
download_item->Cancel(true);
// |download_item| can be NULL if the download ID was invalid or if the
// download is not currently active. Either way, it's not a failure.
@@ -1032,6 +1121,52 @@ bool DownloadsEraseFunction::RunImpl() {
return true;
}
+DownloadsRemoveFileFunction::DownloadsRemoveFileFunction() {}
+
+DownloadsRemoveFileFunction::~DownloadsRemoveFileFunction() {
+ if (item_) {
+ item_->RemoveObserver(this);
+ item_ = NULL;
+ }
+}
+
+bool DownloadsRemoveFileFunction::RunImpl() {
+ scoped_ptr<extensions::api::downloads::RemoveFile::Params> params(
+ extensions::api::downloads::RemoveFile::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+ DownloadItem* download_item = GetDownload(
+ profile(), include_incognito(), params->download_id);
+ if (InvalidId(download_item, &error_) ||
+ Fault((download_item->GetState() != DownloadItem::COMPLETE),
+ errors::kNotComplete, &error_) ||
+ Fault(download_item->GetFileExternallyRemoved(),
+ errors::kFileAlreadyDeleted, &error_))
+ return false;
+ item_ = download_item;
+ item_->AddObserver(this);
+ RecordApiFunctions(DOWNLOADS_FUNCTION_REMOVE_FILE);
+ download_item->DeleteFile();
+ return true;
+}
+
+void DownloadsRemoveFileFunction::OnDownloadUpdated(DownloadItem* download) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(item_, download);
+ if (!item_->GetFileExternallyRemoved())
+ return;
+ item_->RemoveObserver(this);
+ item_ = NULL;
+ SendResponse(true);
+}
+
+void DownloadsRemoveFileFunction::OnDownloadDestroyed(DownloadItem* download) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK_EQ(item_, download);
+ item_->RemoveObserver(this);
+ item_ = NULL;
+ SendResponse(true);
+}
+
DownloadsAcceptDangerFunction::DownloadsAcceptDangerFunction() {}
DownloadsAcceptDangerFunction::~DownloadsAcceptDangerFunction() {}
@@ -1040,16 +1175,16 @@ bool DownloadsAcceptDangerFunction::RunImpl() {
scoped_ptr<extensions::api::downloads::AcceptDanger::Params> params(
extensions::api::downloads::AcceptDanger::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
- DownloadItem* download_item = GetDownloadIfInProgress(
+ DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
content::WebContents* web_contents =
dispatcher()->delegate()->GetVisibleWebContents();
- if (!download_item ||
- !download_item->IsDangerous() ||
- !web_contents) {
- error_ = download_extension_errors::kInvalidOperationError;
+ if (InvalidId(download_item, &error_) ||
+ Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
+ errors::kNotInProgress, &error_) ||
+ Fault(!download_item->IsDangerous(), errors::kNotDangerous, &error_) ||
+ Fault(!web_contents, errors::kInvisibleContext, &error_))
return false;
- }
RecordApiFunctions(DOWNLOADS_FUNCTION_ACCEPT_DANGER);
// DownloadDangerPrompt displays a modal dialog using native widgets that the
// user must either accept or cancel. It cannot be scripted.
@@ -1058,20 +1193,29 @@ bool DownloadsAcceptDangerFunction::RunImpl() {
web_contents,
true,
base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback,
- this, true, params->download_id),
- base::Bind(&DownloadsAcceptDangerFunction::DangerPromptCallback,
- this, false, params->download_id));
+ this, params->download_id));
// DownloadDangerPrompt deletes itself
return true;
}
void DownloadsAcceptDangerFunction::DangerPromptCallback(
- bool accept, int download_id) {
- if (accept) {
- DownloadItem* download_item = GetDownloadIfInProgress(
- profile(), include_incognito(), download_id);
- if (download_item)
+ int download_id, DownloadDangerPrompt::Action action) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DownloadItem* download_item = GetDownload(
+ profile(), include_incognito(), download_id);
+ if (InvalidId(download_item, &error_) ||
+ Fault(download_item->GetState() != DownloadItem::IN_PROGRESS,
+ errors::kNotInProgress, &error_))
+ return;
+ switch (action) {
+ case DownloadDangerPrompt::ACCEPT:
download_item->ValidateDangerousDownload();
+ break;
+ case DownloadDangerPrompt::CANCEL:
+ download_item->Remove();
+ break;
+ case DownloadDangerPrompt::DISMISS:
+ break;
}
SendResponse(error_.empty());
}
@@ -1086,15 +1230,27 @@ bool DownloadsShowFunction::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
- if (!download_item) {
- error_ = download_extension_errors::kInvalidOperationError;
+ if (InvalidId(download_item, &error_))
return false;
- }
download_item->ShowDownloadInShell();
RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW);
return true;
}
+DownloadsShowDefaultFolderFunction::DownloadsShowDefaultFolderFunction() {}
+
+DownloadsShowDefaultFolderFunction::~DownloadsShowDefaultFolderFunction() {}
+
+bool DownloadsShowDefaultFolderFunction::RunImpl() {
+ DownloadManager* manager = NULL;
+ DownloadManager* incognito_manager = NULL;
+ GetManagers(profile(), include_incognito(), &manager, &incognito_manager);
+ platform_util::OpenItem(DownloadPrefs::FromDownloadManager(
+ manager)->DownloadPath());
+ RecordApiFunctions(DOWNLOADS_FUNCTION_SHOW_DEFAULT_FOLDER);
+ return true;
+}
+
DownloadsOpenFunction::DownloadsOpenFunction() {}
DownloadsOpenFunction::~DownloadsOpenFunction() {}
@@ -1105,12 +1261,13 @@ bool DownloadsOpenFunction::RunImpl() {
EXTENSION_FUNCTION_VALIDATE(params.get());
DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
- if (!download_item || download_item->GetState() != DownloadItem::COMPLETE ||
- !GetExtension()->HasAPIPermission(
- extensions::APIPermission::kDownloadsOpen)) {
- error_ = download_extension_errors::kInvalidOperationError;
+ if (InvalidId(download_item, &error_) ||
+ Fault(download_item->GetState() != DownloadItem::COMPLETE,
+ errors::kNotComplete, &error_) ||
+ Fault(!GetExtension()->HasAPIPermission(
+ extensions::APIPermission::kDownloadsOpen),
+ errors::kOpenPermission, &error_))
return false;
- }
download_item->OpenDownload();
RecordApiFunctions(DOWNLOADS_FUNCTION_OPEN);
return true;
@@ -1128,10 +1285,9 @@ bool DownloadsDragFunction::RunImpl() {
profile(), include_incognito(), params->download_id);
content::WebContents* web_contents =
dispatcher()->delegate()->GetVisibleWebContents();
- if (!download_item || !web_contents) {
- error_ = download_extension_errors::kInvalidOperationError;
+ if (InvalidId(download_item, &error_) ||
+ Fault(!web_contents, errors::kInvisibleContext, &error_))
return false;
- }
RecordApiFunctions(DOWNLOADS_FUNCTION_DRAG);
gfx::Image* icon = g_browser_process->icon_manager()->LookupIconFromFilepath(
download_item->GetTargetFilePath(), IconLoader::NORMAL);
@@ -1168,10 +1324,10 @@ bool DownloadsGetFileIconFunction::RunImpl() {
icon_size = *options->size.get();
DownloadItem* download_item = GetDownload(
profile(), include_incognito(), params->download_id);
- if (!download_item || download_item->GetTargetFilePath().empty()) {
- error_ = download_extension_errors::kInvalidOperationError;
+ if (InvalidId(download_item, &error_) ||
+ Fault(download_item->GetTargetFilePath().empty(),
+ errors::kEmptyFile, &error_))
return false;
- }
// In-progress downloads return the intermediate filename for GetFullPath()
// which doesn't have the final extension. Therefore a good file icon can't be
// found, so use GetTargetFilePath() instead.
@@ -1186,13 +1342,13 @@ bool DownloadsGetFileIconFunction::RunImpl() {
void DownloadsGetFileIconFunction::OnIconURLExtracted(const std::string& url) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- if (url.empty()) {
- error_ = download_extension_errors::kIconNotFoundError;
- } else {
- RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON);
- SetResult(base::Value::CreateStringValue(url));
+ if (Fault(url.empty(), errors::kIconNotFound, &error_)) {
+ SendResponse(false);
+ return;
}
- SendResponse(error_.empty());
+ RecordApiFunctions(DOWNLOADS_FUNCTION_GET_FILE_ICON);
+ SetResult(base::Value::CreateStringValue(url));
+ SendResponse(true);
}
ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(
@@ -1267,7 +1423,14 @@ void ExtensionDownloadsEventRouter::OnDeterminingFilename(
json);
if (!any_determiners) {
data->ClearPendingDeterminers();
- no_change.Run();
+ if (!data->creator_suggested_filename().empty()) {
+ change.Run(data->creator_suggested_filename(),
+ ConvertConflictAction(data->creator_conflict_action()));
+ // If all listeners are removed, don't keep |data| around.
+ data->ResetCreatorSuggestion();
+ } else {
+ no_change.Run();
+ }
}
}
@@ -1281,28 +1444,19 @@ bool ExtensionDownloadsEventRouter::DetermineFilename(
std::string* error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DownloadItem* item = GetDownload(profile, include_incognito, download_id);
- if (!item) {
- *error = download_extension_errors::kInvalidOperationError;
- return false;
- }
ExtensionDownloadsEventRouterData* data =
- ExtensionDownloadsEventRouterData::Get(item);
- if (!data) {
- *error = download_extension_errors::kInvalidOperationError;
- return false;
- }
+ item ? ExtensionDownloadsEventRouterData::Get(item) : NULL;
// maxListeners=1 in downloads.idl and suggestCallback in
// downloads_custom_bindings.js should prevent duplicate DeterminerCallback
// calls from the same renderer, but an extension may have more than one
// renderer, so don't DCHECK(!reported).
- if (data->DeterminerAlreadyReported(ext_id)) {
- *error = download_extension_errors::kTooManyListenersError;
+ if (InvalidId(item, error) ||
+ Fault(item->GetState() != DownloadItem::IN_PROGRESS,
+ errors::kNotInProgress, error) ||
+ Fault(!data, errors::kUnexpectedDeterminer, error) ||
+ Fault(data->DeterminerAlreadyReported(ext_id),
+ errors::kTooManyListeners, error))
return false;
- }
- if (item->GetState() != DownloadItem::IN_PROGRESS) {
- *error = download_extension_errors::kInvalidOperationError;
- return false;
- }
base::FilePath::StringType filename_str(const_filename.value());
// Allow windows-style directory separators on all platforms.
std::replace(filename_str.begin(), filename_str.end(),
@@ -1311,17 +1465,13 @@ bool ExtensionDownloadsEventRouter::DetermineFilename(
bool valid_filename = net::IsSafePortableRelativePath(filename);
filename = (valid_filename ? filename.NormalizePathSeparators() :
base::FilePath());
- if (!data->DeterminerCallback(ext_id, filename, conflict_action)) {
- // Nobody expects this ext_id!
- *error = download_extension_errors::kInvalidOperationError;
+ // If the invalid filename check is moved to before DeterminerCallback(), then
+ // it will block forever waiting for this ext_id to report.
+ if (Fault(!data->DeterminerCallback(ext_id, filename, conflict_action),
+ errors::kUnexpectedDeterminer, error) ||
+ Fault((!const_filename.empty() && !valid_filename),
+ errors::kInvalidFilename, error))
return false;
- }
- if (!const_filename.empty() && !valid_filename) {
- // If this is moved to before DeterminerCallback(), then it will block
- // forever waiting for this ext_id to report.
- *error = download_extension_errors::kInvalidFilenameError;
- return false;
- }
return true;
}
@@ -1357,7 +1507,8 @@ void ExtensionDownloadsEventRouter::OnListenerRemoved(
// should proceed.
data->DeterminerRemoved(details.extension_id);
}
- if (!any_listeners) {
+ if (!any_listeners &&
+ data->creator_suggested_filename().empty()) {
ExtensionDownloadsEventRouterData::Remove(*iter);
}
}
@@ -1427,7 +1578,7 @@ void ExtensionDownloadsEventRouter::OnDownloadUpdated(
for (base::DictionaryValue::Iterator iter(*new_json.get());
!iter.IsAtEnd(); iter.Advance()) {
new_fields.insert(iter.key());
- if (iter.key() != kBytesReceivedKey) {
+ if (IsDownloadDeltaField(iter.key())) {
const base::Value* old_value = NULL;
if (!data->json().HasKey(iter.key()) ||
(data->json().Get(iter.key(), &old_value) &&
@@ -1444,7 +1595,9 @@ void ExtensionDownloadsEventRouter::OnDownloadUpdated(
// difference in |delta|.
for (base::DictionaryValue::Iterator iter(data->json());
!iter.IsAtEnd(); iter.Advance()) {
- if (new_fields.find(iter.key()) == new_fields.end()) {
+ if ((new_fields.find(iter.key()) == new_fields.end()) &&
+ IsDownloadDeltaField(iter.key())) {
+ // estimatedEndTime disappears after completion, but bytesReceived stays.
delta->Set(iter.key() + ".previous", iter.value().DeepCopy());
changed = true;
}
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.h b/chrome/browser/extensions/api/downloads/downloads_api.h
index 77b7777..465148c1 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.h
+++ b/chrome/browser/extensions/api/downloads/downloads_api.h
@@ -12,6 +12,7 @@
#include "base/strings/string16.h"
#include "base/values.h"
#include "chrome/browser/download/all_download_item_notifier.h"
+#include "chrome/browser/download/download_danger_prompt.h"
#include "chrome/browser/download/download_path_reservation_tracker.h"
#include "chrome/browser/extensions/event_router.h"
#include "chrome/browser/extensions/extension_function.h"
@@ -34,18 +35,26 @@ class ResourceDispatcherHost;
namespace download_extension_errors {
// Errors that can be returned through chrome.runtime.lastError.message.
-extern const char kGenericError[];
-extern const char kIconNotFoundError[];
-extern const char kInvalidDangerTypeError[];
-extern const char kInvalidFilenameError[];
-extern const char kInvalidFilterError[];
-extern const char kInvalidOperationError[];
-extern const char kInvalidOrderByError[];
+extern const char kEmptyFile[];
+extern const char kFileAlreadyDeleted[];
+extern const char kIconNotFound[];
+extern const char kInvalidDangerType[];
+extern const char kInvalidFilename[];
+extern const char kInvalidFilter[];
+extern const char kInvalidHeader[];
+extern const char kInvalidId[];
+extern const char kInvalidOrderBy[];
extern const char kInvalidQueryLimit[];
-extern const char kInvalidStateError[];
-extern const char kInvalidURLError[];
-extern const char kNotImplementedError[];
-extern const char kTooManyListenersError[];
+extern const char kInvalidState[];
+extern const char kInvalidURL[];
+extern const char kInvisibleContext[];
+extern const char kNotComplete[];
+extern const char kNotDangerous[];
+extern const char kNotInProgress[];
+extern const char kNotResumable[];
+extern const char kOpenPermission[];
+extern const char kTooManyListeners[];
+extern const char kUnexpectedDeterminer[];
} // namespace download_extension_errors
@@ -60,7 +69,12 @@ class DownloadsDownloadFunction : public AsyncExtensionFunction {
virtual ~DownloadsDownloadFunction();
private:
- void OnStarted(content::DownloadItem* item, net::Error error);
+ void OnStarted(
+ const base::FilePath& creator_suggested_filename,
+ extensions::api::downloads::FilenameConflictAction
+ creator_conflict_action,
+ content::DownloadItem* item,
+ net::Error error);
DISALLOW_COPY_AND_ASSIGN(DownloadsDownloadFunction);
};
@@ -130,6 +144,25 @@ class DownloadsEraseFunction : public SyncExtensionFunction {
DISALLOW_COPY_AND_ASSIGN(DownloadsEraseFunction);
};
+class DownloadsRemoveFileFunction : public AsyncExtensionFunction,
+ public content::DownloadItem::Observer {
+ public:
+ DECLARE_EXTENSION_FUNCTION("downloads.removeFile", DOWNLOADS_REMOVEFILE)
+ DownloadsRemoveFileFunction();
+ virtual bool RunImpl() OVERRIDE;
+
+ protected:
+ virtual ~DownloadsRemoveFileFunction();
+
+ private:
+ virtual void OnDownloadUpdated(content::DownloadItem* item) OVERRIDE;
+ virtual void OnDownloadDestroyed(content::DownloadItem* item) OVERRIDE;
+
+ content::DownloadItem* item_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadsRemoveFileFunction);
+};
+
class DownloadsAcceptDangerFunction : public AsyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("downloads.acceptDanger", DOWNLOADS_ACCEPTDANGER)
@@ -138,7 +171,8 @@ class DownloadsAcceptDangerFunction : public AsyncExtensionFunction {
protected:
virtual ~DownloadsAcceptDangerFunction();
- void DangerPromptCallback(bool accept, int download_id);
+ void DangerPromptCallback(int download_id,
+ DownloadDangerPrompt::Action action);
private:
DISALLOW_COPY_AND_ASSIGN(DownloadsAcceptDangerFunction);
@@ -157,6 +191,20 @@ class DownloadsShowFunction : public AsyncExtensionFunction {
DISALLOW_COPY_AND_ASSIGN(DownloadsShowFunction);
};
+class DownloadsShowDefaultFolderFunction : public AsyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION(
+ "downloads.showDefaultFolder", DOWNLOADS_SHOWDEFAULTFOLDER)
+ DownloadsShowDefaultFolderFunction();
+ virtual bool RunImpl() OVERRIDE;
+
+ protected:
+ virtual ~DownloadsShowDefaultFolderFunction();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DownloadsShowDefaultFolderFunction);
+};
+
class DownloadsOpenFunction : public SyncExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("downloads.open", DOWNLOADS_OPEN)
diff --git a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
index 93a40f2..5060617 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api_unittest.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api_browsertest.cc
@@ -62,6 +62,10 @@ using content::URLRequestSlowDownloadJob;
namespace events = extensions::event_names;
+namespace errors = download_extension_errors;
+
+namespace api = extensions::api::downloads;
+
namespace {
// Comparator that orders download items by their ID. Can be used with
@@ -349,19 +353,22 @@ class DownloadExtensionTest : public ExtensionApiTest {
current_browser()->profile(), event_name, json_args);
}
- bool WaitForInterruption(DownloadItem* item, int expected_error,
- const std::string& on_created_event) {
+ bool WaitForInterruption(
+ DownloadItem* item,
+ content::DownloadInterruptReason expected_error,
+ const std::string& on_created_event) {
if (!WaitFor(events::kOnDownloadCreated, on_created_event))
return false;
// Now, onCreated is always fired before interruption.
return WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
- " \"error\": {\"current\": %d},"
- " \"state\": {"
- " \"previous\": \"in_progress\","
- " \"current\": \"interrupted\"}}]",
- item->GetId(),
- expected_error));
+ " \"error\": {\"current\": \"%s\"},"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"interrupted\"}}]",
+ item->GetId(),
+ content::InterruptReasonDebugString(
+ expected_error).c_str()));
}
void ClearEvents() {
@@ -904,12 +911,42 @@ bool ItemIsInterrupted(DownloadItem* item) {
return item->GetState() == DownloadItem::INTERRUPTED;
}
+content::DownloadInterruptReason InterruptReasonExtensionToContent(
+ api::InterruptReason error) {
+ switch (error) {
+ case api::INTERRUPT_REASON_NONE:
+ return content::DOWNLOAD_INTERRUPT_REASON_NONE;
+#define INTERRUPT_REASON(name, value) \
+ case api::INTERRUPT_REASON_##name: \
+ return content::DOWNLOAD_INTERRUPT_REASON_##name;
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+ }
+ NOTREACHED();
+ return content::DOWNLOAD_INTERRUPT_REASON_NONE;
+}
+
+api::InterruptReason InterruptReasonContentToExtension(
+ content::DownloadInterruptReason error) {
+ switch (error) {
+ case content::DOWNLOAD_INTERRUPT_REASON_NONE:
+ return api::INTERRUPT_REASON_NONE;
+#define INTERRUPT_REASON(name, value) \
+ case content::DOWNLOAD_INTERRUPT_REASON_##name: \
+ return api::INTERRUPT_REASON_##name;
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+ }
+ NOTREACHED();
+ return api::INTERRUPT_REASON_NONE;
+}
+
} // namespace
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
DownloadExtensionTest_Open) {
LoadExtension("downloads_split");
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
+ EXPECT_STREQ(errors::kInvalidId,
RunFunctionAndReturnError(
new DownloadsOpenFunction(),
"[-42]").c_str());
@@ -925,7 +962,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
" \"paused\": false,"
" \"url\": \"%s\"}]",
download_item->GetURL().spec().c_str())));
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
+ EXPECT_STREQ(errors::kNotComplete,
RunFunctionAndReturnError(
new DownloadsOpenFunction(),
DownloadItemIdAsArgList(download_item)).c_str());
@@ -978,29 +1015,25 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
DownloadItemIdAsArgList(download_item)));
EXPECT_EQ(DownloadItem::CANCELLED, download_item->GetState());
- // Calling paused on a non-active download yields kInvalidOperationError.
+ // Calling paused on a non-active download yields kInvalidId.
std::string error = RunFunctionAndReturnError(
new DownloadsPauseFunction(), DownloadItemIdAsArgList(download_item));
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kNotInProgress, error.c_str());
- // Calling resume on a non-active download yields kInvalidOperationError
+ // Calling resume on a non-active download yields kInvalidId
error = RunFunctionAndReturnError(
new DownloadsResumeFunction(), DownloadItemIdAsArgList(download_item));
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kNotResumable, error.c_str());
- // Calling paused on a non-existent download yields kInvalidOperationError.
+ // Calling paused on a non-existent download yields kInvalidId.
error = RunFunctionAndReturnError(
new DownloadsPauseFunction(), "[-42]");
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kInvalidId, error.c_str());
- // Calling resume on a non-existent download yields kInvalidOperationError
+ // Calling resume on a non-existent download yields kInvalidId
error = RunFunctionAndReturnError(
new DownloadsResumeFunction(), "[-42]");
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kInvalidId, error.c_str());
int id = download_item->GetId();
scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
@@ -1096,16 +1129,16 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
IconLoader::NORMAL,
std::string()),
args32);
- EXPECT_STREQ(download_extension_errors::kIconNotFoundError, error.c_str());
+ EXPECT_STREQ(errors::kIconNotFound, error.c_str());
- // Once the download item is deleted, we should return kInvalidOperationError.
+ // Once the download item is deleted, we should return kInvalidId.
int id = download_item->GetId();
download_item->Remove();
download_item = NULL;
EXPECT_EQ(static_cast<DownloadItem*>(NULL),
GetCurrentManager()->GetDownload(id));
error = RunFunctionAndReturnError(new DownloadsGetFileIconFunction(), args32);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
+ EXPECT_STREQ(errors::kInvalidId,
error.c_str());
}
@@ -1146,8 +1179,6 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
&result_string));
EXPECT_STREQ("hello", result_string.c_str());
}
-
- // The temporary files should be cleaned up when the base::ScopedTempDir is removed.
}
// Test passing the empty query to search().
@@ -1244,7 +1275,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
&items));
scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
- new DownloadsSearchFunction(), "[{\"orderBy\": \"filename\"}]"));
+ new DownloadsSearchFunction(), "[{\"orderBy\": [\"filename\"]}]"));
ASSERT_TRUE(result.get());
base::ListValue* result_list = NULL;
ASSERT_TRUE(result->GetAsList(&result_list));
@@ -1277,7 +1308,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
&items));
scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
- new DownloadsSearchFunction(), "[{\"orderBy\": \"\"}]"));
+ new DownloadsSearchFunction(), "[{\"orderBy\": []}]"));
ASSERT_TRUE(result.get());
base::ListValue* result_list = NULL;
ASSERT_TRUE(result->GetAsList(&result_list));
@@ -1354,15 +1385,15 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
DownloadExtensionTest_SearchInvalid) {
std::string error = RunFunctionAndReturnError(
new DownloadsSearchFunction(), "[{\"filenameRegex\": \"(\"}]");
- EXPECT_STREQ(download_extension_errors::kInvalidFilterError,
+ EXPECT_STREQ(errors::kInvalidFilter,
error.c_str());
error = RunFunctionAndReturnError(
- new DownloadsSearchFunction(), "[{\"orderBy\": \"goat\"}]");
- EXPECT_STREQ(download_extension_errors::kInvalidOrderByError,
+ new DownloadsSearchFunction(), "[{\"orderBy\": [\"goat\"]}]");
+ EXPECT_STREQ(errors::kInvalidOrderBy,
error.c_str());
error = RunFunctionAndReturnError(
new DownloadsSearchFunction(), "[{\"limit\": -1}]");
- EXPECT_STREQ(download_extension_errors::kInvalidQueryLimit,
+ EXPECT_STREQ(errors::kInvalidQueryLimit,
error.c_str());
}
@@ -1388,7 +1419,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
new DownloadsSearchFunction(), "[{"
"\"state\": \"complete\", "
"\"danger\": \"content\", "
- "\"orderBy\": \"filename\", "
+ "\"orderBy\": [\"filename\"], "
"\"limit\": 1}]"));
ASSERT_TRUE(result.get());
base::ListValue* result_list = NULL;
@@ -1468,16 +1499,16 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
// Pausing/Resuming the off-record item while on the record should return an
// error. Cancelling "non-existent" downloads is not an error.
error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
+ EXPECT_STREQ(errors::kInvalidId,
error.c_str());
error = RunFunctionAndReturnError(new DownloadsResumeFunction(),
off_item_arg);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
+ EXPECT_STREQ(errors::kInvalidId,
error.c_str());
error = RunFunctionAndReturnError(
new DownloadsGetFileIconFunction(),
base::StringPrintf("[%d, {}]", off_item->GetId()));
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
+ EXPECT_STREQ(errors::kInvalidId,
error.c_str());
GoOffTheRecord();
@@ -1509,11 +1540,9 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), on_item_arg));
EXPECT_EQ(DownloadItem::CANCELLED, on_item->GetState());
error = RunFunctionAndReturnError(new DownloadsPauseFunction(), on_item_arg);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kNotInProgress, error.c_str());
error = RunFunctionAndReturnError(new DownloadsResumeFunction(), on_item_arg);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kNotResumable, error.c_str());
EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg));
EXPECT_TRUE(off_item->IsPaused());
EXPECT_TRUE(RunFunction(new DownloadsPauseFunction(), off_item_arg));
@@ -1528,14 +1557,11 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState());
EXPECT_TRUE(RunFunction(new DownloadsCancelFunction(), off_item_arg));
EXPECT_EQ(DownloadItem::CANCELLED, off_item->GetState());
- error = RunFunctionAndReturnError(new DownloadsPauseFunction(),
- off_item_arg);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ error = RunFunctionAndReturnError(new DownloadsPauseFunction(), off_item_arg);
+ EXPECT_STREQ(errors::kNotInProgress, error.c_str());
error = RunFunctionAndReturnError(new DownloadsResumeFunction(),
off_item_arg);
- EXPECT_STREQ(download_extension_errors::kInvalidOperationError,
- error.c_str());
+ EXPECT_STREQ(errors::kNotResumable, error.c_str());
}
// Test that we can start a download and that the correct sequence of events is
@@ -1673,7 +1699,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
for (size_t index = 0; index < arraysize(kUnsafeHeaders); ++index) {
std::string download_url = test_server()->GetURL("slow?0").spec();
- EXPECT_STREQ(download_extension_errors::kGenericError,
+ EXPECT_STREQ(errors::kInvalidHeader,
RunFunctionAndReturnError(new DownloadsDownloadFunction(),
base::StringPrintf(
"[{\"url\": \"%s\","
@@ -1687,8 +1713,6 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
}
}
-// Test that subdirectories (slashes) are disallowed in filenames.
-// TODO(benjhayden) Update this when subdirectories are supported.
IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
DownloadExtensionTest_Download_Subdirectory) {
LoadExtension("downloads_split");
@@ -1697,12 +1721,39 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
std::string download_url = test_server()->GetURL("slow?0").spec();
GoOnTheRecord();
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError,
- RunFunctionAndReturnError(new DownloadsDownloadFunction(),
- base::StringPrintf(
- "[{\"url\": \"%s\","
- " \"filename\": \"sub/dir/ect/ory.txt\"}]",
- download_url.c_str())).c_str());
+ scoped_ptr<base::Value> result(RunFunctionAndReturnResult(
+ new DownloadsDownloadFunction(), base::StringPrintf(
+ "[{\"url\": \"%s\","
+ " \"filename\": \"sub/dir/ect/ory.txt\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(result.get());
+ int result_id = -1;
+ ASSERT_TRUE(result->GetAsInteger(&result_id));
+ DownloadItem* item = GetCurrentManager()->GetDownload(result_id);
+ ASSERT_TRUE(item);
+ ScopedCancellingItem canceller(item);
+ ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
+
+ ASSERT_TRUE(WaitFor(events::kOnDownloadCreated,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/plain\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"filename\": {"
+ " \"previous\": \"\","
+ " \"current\": \"%s\"}}]",
+ result_id,
+ GetFilename("sub/dir/ect/ory.txt").c_str())));
+ ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
+ base::StringPrintf("[{\"id\": %d,"
+ " \"state\": {"
+ " \"previous\": \"in_progress\","
+ " \"current\": \"complete\"}}]",
+ result_id)));
}
// Test that invalid filenames are disallowed.
@@ -1714,7 +1765,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
std::string download_url = test_server()->GetURL("slow?0").spec();
GoOnTheRecord();
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError,
+ EXPECT_STREQ(errors::kInvalidFilename,
RunFunctionAndReturnError(new DownloadsDownloadFunction(),
base::StringPrintf(
"[{\"url\": \"%s\","
@@ -1732,14 +1783,14 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
"foo bar",
"../hello",
"/hello",
- "google.com/",
"http://",
"#frag",
"foo/bar.html#frag",
+ "google.com/",
};
for (size_t index = 0; index < arraysize(kInvalidURLs); ++index) {
- EXPECT_STREQ(download_extension_errors::kInvalidURLError,
+ EXPECT_STREQ(errors::kInvalidURL,
RunFunctionAndReturnError(new DownloadsDownloadFunction(),
base::StringPrintf(
"[{\"url\": \"%s\"}]", kInvalidURLs[index])).c_str())
@@ -1920,13 +1971,15 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
ScopedCancellingItem canceller(item);
ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
- ASSERT_TRUE(WaitForInterruption(item, 30, base::StringPrintf(
- "[{\"danger\": \"safe\","
- " \"incognito\": false,"
- " \"mime\": \"text/html\","
- " \"paused\": false,"
- " \"url\": \"%s\"}]",
- download_url.c_str())));
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_FAILED,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"text/html\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
}
// Test that DownloadsDownloadFunction propagates |headers| to the URLRequest.
@@ -2003,14 +2056,16 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
ScopedCancellingItem canceller(item);
ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
- ASSERT_TRUE(WaitForInterruption(item, 33, base::StringPrintf(
- "[{\"danger\": \"safe\","
- " \"incognito\": false,"
- " \"bytesReceived\": 0,"
- " \"mime\": \"\","
- " \"paused\": false,"
- " \"url\": \"%s\"}]",
- download_url.c_str())));
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"bytesReceived\": 0,"
+ " \"mime\": \"\","
+ " \"paused\": false,"
+ " \"url\": \"%s\"}]",
+ download_url.c_str())));
}
// Test that DownloadsDownloadFunction propagates the Authorization header
@@ -2129,15 +2184,17 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
ScopedCancellingItem canceller(item);
ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
- ASSERT_TRUE(WaitForInterruption(item, 33, base::StringPrintf(
- "[{\"danger\": \"safe\","
- " \"incognito\": false,"
- " \"mime\": \"\","
- " \"paused\": false,"
- " \"id\": %d,"
- " \"url\": \"%s\"}]",
- result_id,
- download_url.c_str())));
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"\","
+ " \"paused\": false,"
+ " \"id\": %d,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
}
// Test that downloadPostSuccess would fail if the resource requires the POST
@@ -2168,15 +2225,17 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
ScopedCancellingItem canceller(item);
ASSERT_EQ(download_url, item->GetOriginalUrl().spec());
- ASSERT_TRUE(WaitForInterruption(item, 33, base::StringPrintf(
- "[{\"danger\": \"safe\","
- " \"incognito\": false,"
- " \"mime\": \"\","
- " \"paused\": false,"
- " \"id\": %d,"
- " \"url\": \"%s\"}]",
- result_id,
- download_url.c_str())));
+ ASSERT_TRUE(WaitForInterruption(
+ item,
+ content::DOWNLOAD_INTERRUPT_REASON_SERVER_BAD_CONTENT,
+ base::StringPrintf("[{\"danger\": \"safe\","
+ " \"incognito\": false,"
+ " \"mime\": \"\","
+ " \"paused\": false,"
+ " \"id\": %d,"
+ " \"url\": \"%s\"}]",
+ result_id,
+ download_url.c_str())));
}
// Test that cancel()ing an in-progress download causes its state to transition
@@ -2215,7 +2274,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
item->Cancel(true);
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
- " \"error\": {\"current\": 40},"
+ " \"error\": {\"current\":\"USER_CANCELED\"},"
" \"state\": {"
" \"previous\": \"in_progress\","
" \"current\": \"interrupted\"}}]",
@@ -2324,7 +2383,7 @@ IN_PROC_BROWSER_TEST_F(DownloadExtensionTest,
GetExtensionId(),
result_id,
base::FilePath(),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -2391,7 +2450,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("overridden.swf")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -2399,17 +2458,15 @@ IN_PROC_BROWSER_TEST_F(
base::StringPrintf("[{\"id\": %d,"
" \"danger\": {"
" \"previous\":\"safe\","
- " \"current\":\"file\"},"
- " \"dangerAccepted\": {"
- " \"current\":false}}]",
+ " \"current\":\"file\"}}]",
result_id)));
item->ValidateDangerousDownload();
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
- " \"dangerAccepted\": {"
- " \"previous\":false,"
- " \"current\":true}}]",
+ " \"danger\": {"
+ " \"previous\":\"file\","
+ " \"current\":\"accepted\"}}]",
result_id)));
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
@@ -2468,9 +2525,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("sneaky/../../sneaky.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
" \"filename\": {"
@@ -2533,9 +2590,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("<")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
"[{\"id\": %d,"
" \"filename\": {"
@@ -2599,9 +2656,9 @@ IN_PROC_BROWSER_TEST_F(
result_id,
base::FilePath(FILE_PATH_LITERAL(
"My Computer.{20D04FE0-3AEA-1069-A2D8-08002B30309D}/foo")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
"[{\"id\": %d,"
" \"filename\": {"
@@ -2664,9 +2721,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("con.foo")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged, base::StringPrintf(
"[{\"id\": %d,"
" \"filename\": {"
@@ -2729,9 +2786,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL(".")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
" \"filename\": {"
@@ -2794,9 +2851,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("..")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
" \"filename\": {"
@@ -2859,9 +2916,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
downloads_directory().Append(FILE_PATH_LITERAL("sneaky.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
@@ -2925,9 +2982,9 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("foo/")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
- EXPECT_STREQ(download_extension_errors::kInvalidFilenameError, error.c_str());
+ EXPECT_STREQ(errors::kInvalidFilename, error.c_str());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
@@ -2990,7 +3047,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -3048,7 +3105,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("foo")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_OVERWRITE,
+ api::FILENAME_CONFLICT_ACTION_OVERWRITE,
&error));
EXPECT_EQ("", error);
@@ -3173,7 +3230,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("42.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -3232,7 +3289,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("5.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -3307,7 +3364,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("42.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -3365,7 +3422,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
result_id,
base::FilePath(FILE_PATH_LITERAL("42.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error));
EXPECT_EQ("", error);
@@ -3471,7 +3528,7 @@ IN_PROC_BROWSER_TEST_F(
GetExtensionId(),
item->GetId(),
base::FilePath(FILE_PATH_LITERAL("42.txt")),
- extensions::api::downloads::FILENAME_CONFLICT_ACTION_UNIQUIFY,
+ api::FILENAME_CONFLICT_ACTION_UNIQUIFY,
&error)) << error;
EXPECT_EQ("", error);
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
@@ -3487,7 +3544,7 @@ IN_PROC_BROWSER_TEST_F(
ASSERT_TRUE(interrupted.WaitForEvent());
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
- " \"error\":{\"current\":20},"
+ " \"error\":{\"current\":\"NETWORK_FAILED\"},"
" \"state\":{"
" \"previous\":\"in_progress\","
" \"current\":\"interrupted\"}}]",
@@ -3506,7 +3563,7 @@ IN_PROC_BROWSER_TEST_F(
ASSERT_TRUE(WaitFor(events::kOnDownloadChanged,
base::StringPrintf("[{\"id\": %d,"
- " \"error\":{\"previous\":20},"
+ " \"error\":{\"previous\":\"NETWORK_FAILED\"},"
" \"state\":{"
" \"previous\":\"interrupted\","
" \"current\":\"in_progress\"}}]",
@@ -3542,3 +3599,17 @@ class DownloadsApiTest : public ExtensionApiTest {
IN_PROC_BROWSER_TEST_F(DownloadsApiTest, DownloadsApiTest) {
ASSERT_TRUE(RunExtensionTest("downloads")) << message_;
}
+
+
+TEST(DownloadInterruptReasonEnumsSynced,
+ DownloadInterruptReasonEnumsSynced) {
+#define INTERRUPT_REASON(name, value) \
+ EXPECT_EQ(InterruptReasonContentToExtension( \
+ content::DOWNLOAD_INTERRUPT_REASON_##name), \
+ api::INTERRUPT_REASON_##name); \
+ EXPECT_EQ(InterruptReasonExtensionToContent( \
+ api::INTERRUPT_REASON_##name), \
+ content::DOWNLOAD_INTERRUPT_REASON_##name);
+#include "content/public/browser/download_interrupt_reason_values.h"
+#undef INTERRUPT_REASON
+}
diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h
index 1723089..d3a6e2e 100644
--- a/chrome/browser/extensions/extension_function_histogram_value.h
+++ b/chrome/browser/extensions/extension_function_histogram_value.h
@@ -578,6 +578,8 @@ enum HistogramValue {
SYSTEM_STORAGE_REMOVEAVAILABLECAPACITYWATCH,
SYSTEM_STORAGE_GETALLAVAILABLECAPACITYWATCHES,
SYSTEM_STORAGE_REMOVEALLAVAILABLECAPACITYWATCHES,
+ DOWNLOADS_REMOVEFILE,
+ DOWNLOADS_SHOWDEFAULTFOLDER,
ENUM_BOUNDARY // Last entry: Add new entries above.
};
diff --git a/chrome/browser/ui/webui/downloads_dom_handler.cc b/chrome/browser/ui/webui/downloads_dom_handler.cc
index 72245e1..d31964b 100644
--- a/chrome/browser/ui/webui/downloads_dom_handler.cc
+++ b/chrome/browser/ui/webui/downloads_dom_handler.cc
@@ -518,14 +518,16 @@ void DownloadsDOMHandler::ShowDangerPrompt(
dangerous_item,
GetWebUIWebContents(),
false,
- base::Bind(&DownloadsDOMHandler::DangerPromptAccepted,
- weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId()),
- base::Closure());
+ base::Bind(&DownloadsDOMHandler::DangerPromptDone,
+ weak_ptr_factory_.GetWeakPtr(), dangerous_item->GetId()));
// danger_prompt will delete itself.
DCHECK(danger_prompt);
}
-void DownloadsDOMHandler::DangerPromptAccepted(int download_id) {
+void DownloadsDOMHandler::DangerPromptDone(
+ int download_id, DownloadDangerPrompt::Action action) {
+ if (action != DownloadDangerPrompt::ACCEPT)
+ return;
content::DownloadItem* item = NULL;
if (main_notifier_.GetManager())
item = main_notifier_.GetManager()->GetDownload(download_id);
diff --git a/chrome/browser/ui/webui/downloads_dom_handler.h b/chrome/browser/ui/webui/downloads_dom_handler.h
index 2d3daa2..aee7ed1 100644
--- a/chrome/browser/ui/webui/downloads_dom_handler.h
+++ b/chrome/browser/ui/webui/downloads_dom_handler.h
@@ -11,6 +11,7 @@
#include "base/compiler_specific.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/download/all_download_item_notifier.h"
+#include "chrome/browser/download/download_danger_prompt.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/web_ui_message_handler.h"
@@ -120,7 +121,7 @@ class DownloadsDOMHandler : public content::WebUIMessageHandler,
// Conveys danger acceptance from the DownloadDangerPrompt to the
// DownloadItem.
- void DangerPromptAccepted(int download_id);
+ void DangerPromptDone(int download_id, DownloadDangerPrompt::Action action);
// Returns true if the records of any downloaded items are allowed (and able)
// to be deleted.
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index dad04a8..36c9090 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1290,7 +1290,7 @@
'browser/extensions/api/dns/dns_apitest.cc',
'browser/extensions/api/dns/mock_host_resolver_creator.cc',
'browser/extensions/api/dns/mock_host_resolver_creator.h',
- 'browser/extensions/api/downloads/downloads_api_unittest.cc',
+ 'browser/extensions/api/downloads/downloads_api_browsertest.cc',
'browser/extensions/api/extension_action/browser_action_apitest.cc',
'browser/extensions/api/extension_action/page_action_apitest.cc',
'browser/extensions/api/extension_action/page_as_browser_action_apitest.cc',
diff --git a/chrome/common/extensions/api/_permission_features.json b/chrome/common/extensions/api/_permission_features.json
index 6228593..b6152bb 100644
--- a/chrome/common/extensions/api/_permission_features.json
+++ b/chrome/common/extensions/api/_permission_features.json
@@ -190,7 +190,7 @@
"downloads": {
"channel": "beta",
"extension_types": [
- "extension", "packaged_app"
+ "extension"
]
},
"downloads.open": {
diff --git a/chrome/common/extensions/api/downloads.idl b/chrome/common/extensions/api/downloads.idl
index c427026..ee8b012 100644
--- a/chrome/common/extensions/api/downloads.idl
+++ b/chrome/common/extensions/api/downloads.idl
@@ -22,7 +22,7 @@ namespace downloads {
// <dt>prompt</dt>
// <dd>The user will be prompted with a file chooser dialog.</dd>
// </dl>
- [inline_doc] enum FilenameConflictAction {uniquify, overwrite, prompt};
+ enum FilenameConflictAction {uniquify, overwrite, prompt};
[inline_doc] dictionary FilenameSuggestion {
// The $ref:DownloadItem's new target $ref:DownloadItem.filename, as a path
@@ -32,23 +32,50 @@ namespace downloads {
DOMString filename;
// The action to take if <code>filename</code> already exists.
- FilenameConflictAction? conflict_action;
+ FilenameConflictAction? conflictAction;
};
[inline_doc] enum HttpMethod {GET, POST};
+ enum InterruptReason {
+ FILE_FAILED,
+ FILE_ACCESS_DENIED,
+ FILE_NO_SPACE,
+ FILE_NAME_TOO_LONG,
+ FILE_TOO_LARGE,
+ FILE_VIRUS_INFECTED,
+ FILE_TRANSIENT_ERROR,
+ FILE_BLOCKED,
+ FILE_SECURITY_CHECK_FAILED,
+ FILE_TOO_SHORT,
+ NETWORK_FAILED,
+ NETWORK_TIMEOUT,
+ NETWORK_DISCONNECTED,
+ NETWORK_SERVER_DOWN,
+ SERVER_FAILED,
+ SERVER_NO_RANGE,
+ SERVER_PRECONDITION,
+ SERVER_BAD_CONTENT,
+ USER_CANCELED,
+ USER_SHUTDOWN,
+ CRASH};
+
[inline_doc] dictionary DownloadOptions {
// The URL to download.
DOMString url;
// A file path relative to the Downloads directory to contain the downloaded
- // file. Cannot yet contain subdirectories; support for subdirectories will
- // be implemented before this API is released to the stable channel. See
- // $ref:onDeterminingFilename for how to dynamically suggest a filename
- // after the file's MIME type and a tentative filename have been determined.
+ // file, possibly containing subdirectories. Absolute paths, empty paths,
+ // and paths containing back-references ".." will cause an error.
+ // $ref:onDeterminingFilename allows suggesting a filename after the file's
+ // MIME type and a tentative filename have been determined.
DOMString? filename;
- // Use a file-chooser to allow the user to select a filename.
+ // The action to take if <code>filename</code> already exists.
+ FilenameConflictAction? conflictAction;
+
+ // Use a file-chooser to allow the user to select a filename regardless of
+ // whether <code>filename</code> is set or already exists.
boolean? saveAs;
// The HTTP method to use if the URL uses the HTTP[S] protocol.
@@ -103,6 +130,9 @@ namespace downloads {
// Absolute URL.
DOMString url;
+ // Absolute URL.
+ DOMString referrer;
+
// Absolute local path.
DOMString filename;
@@ -114,9 +144,6 @@ namespace downloads {
// suspicious.
DangerType danger;
- // True if the user has accepted the download's danger.
- boolean? dangerAccepted;
-
// The file's MIME type.
DOMString mime;
@@ -132,6 +159,13 @@ namespace downloads {
// console.log(new Date(item.endTime))})})</code>
DOMString? endTime;
+ // Estimated time when the download will complete in ISO 8601 format. May be
+ // passed directly to the Date constructor:
+ // <code>chrome.downloads.search({},
+ // function(items){items.forEach(function(item){if (item.estimatedEndTime)
+ // console.log(new Date(item.estimatedEndTime))})})</code>
+ DOMString? estimatedEndTime;
+
// Indicates whether the download is progressing, interrupted, or complete.
State state;
@@ -139,8 +173,17 @@ namespace downloads {
// connection open.
boolean paused;
- // Number indicating why a download was interrupted.
- long? error;
+ // True if the download is in progress and paused, or else if it is
+ // interrupted and can be resumed starting from where it was interrupted.
+ boolean canResume;
+
+ // Why the download was interrupted. Several kinds of HTTP errors may be
+ // grouped under one of the errors beginning with <code>SERVER_</code>.
+ // Errors relating to the network begin with <code>NETWORK_</code>, errors
+ // relating to the process of writing the file to the file system begin with
+ // <code>FILE_</code>, and interruptions initiated by the user begin with
+ // <code>USER_</code>.
+ InterruptReason? error;
// Number of bytes received so far from the host, without considering file
// compression.
@@ -166,12 +209,11 @@ namespace downloads {
};
[inline_doc] dictionary DownloadQuery {
- // This space-separated string of search terms that may be grouped using
- // quotation marks limits results to
- // $ref:DownloadItem whose <code>filename</code>
- // or <code>url</code> contain all of the search terms that do not begin with a dash '-'
- // and none of the search terms that do begin with a dash.
- DOMString? query;
+ // This array of search terms limits results to $ref:DownloadItem whose
+ // <code>filename</code> or <code>url</code> contain all of the search terms
+ // that do not begin with a dash '-' and none of the search terms that do
+ // begin with a dash.
+ DOMString[]? query;
// Limits results to $ref:DownloadItem that
// started before the given ms since the epoch.
@@ -205,18 +247,17 @@ namespace downloads {
// <code>url</code> matches the given regular expression.
DOMString? urlRegex;
- // Setting this integer limits the number of results. Otherwise, all
- // matching $ref:DownloadItem will be returned.
+ // The maximum number of matching $ref:DownloadItem returned. Defaults to
+ // 1000. Set to 0 in order to return all matching $ref:DownloadItem. See
+ // $ref:search for how to page through results.
long? limit;
- // Setting this string to a $ref:DownloadItem
- // property sorts the $ref:DownloadItem prior
- // to applying the above filters. For example, setting
- // <code>orderBy='startTime'</code> sorts the
- // $ref:DownloadItem by their start time in
- // ascending order. To specify descending order, prefix <code>orderBy</code>
- // with a hyphen: '-startTime'.
- DOMString? orderBy;
+ // Set elements of this array to $ref:DownloadItem properties in order to
+ // sort search results. For example, setting
+ // <code>orderBy=['startTime']</code> sorts the $ref:DownloadItem by their
+ // start time in ascending order. To specify descending order, prefix with a
+ // hyphen: '-startTime'.
+ DOMString[]? orderBy;
// The <code>id</code> of the $ref:DownloadItem to query.
long? id;
@@ -231,9 +272,6 @@ namespace downloads {
// suspicious.
DangerType? danger;
- // True if the user has accepted the download's danger.
- boolean? dangerAccepted;
-
// The file's MIME type.
DOMString? mime;
@@ -250,8 +288,8 @@ namespace downloads {
// connection open.
boolean? paused;
- // Number indicating why a download was interrupted.
- long? error;
+ // Why a download was interrupted.
+ InterruptReason? error;
// Number of bytes received so far from the host, without considering file
// compression.
@@ -298,9 +336,6 @@ namespace downloads {
// The change in <code>danger</code>, if any.
StringDelta? danger;
- // The change in <code>dangerAccepted</code>, if any.
- BooleanDelta? dangerAccepted;
-
// The change in <code>mime</code>, if any.
StringDelta? mime;
@@ -313,11 +348,14 @@ namespace downloads {
// The change in <code>state</code>, if any.
StringDelta? state;
+ // The change in <code>canResume</code>, if any.
+ BooleanDelta? canResume;
+
// The change in <code>paused</code>, if any.
BooleanDelta? paused;
// The change in <code>error</code>, if any.
- LongDelta? error;
+ StringDelta? error;
// The change in <code>totalBytes</code>, if any.
LongDelta? totalBytes;
@@ -360,27 +398,24 @@ namespace downloads {
static void download(DownloadOptions options,
optional DownloadCallback callback);
- // Find $ref:DownloadItem. Set
- // <code>query</code> to the empty object to get all
- // $ref:DownloadItem. To get a specific
- // $ref:DownloadItem, set only the <code>id</code>
- // field.
+ // Find $ref:DownloadItem. Set <code>query</code> to the empty object to get
+ // all $ref:DownloadItem. To get a specific $ref:DownloadItem, set only the
+ // <code>id</code> field. To page through a large number of items, set
+ // <code>orderBy: ['-startTime']</code>, set <code>limit</code> to the
+ // number of items per page, and set <code>startedAfter</code> to the
+ // <code>startTime</code> of the last item from the last page.
static void search(DownloadQuery query, SearchCallback callback);
// Pause the download. If the request was successful the download is in a
- // paused state. Otherwise
- // $ref:runtime.lastError
- // contains an error message. The request will fail if the download is not
- // active.
+ // paused state. Otherwise $ref:runtime.lastError contains an error message.
+ // The request will fail if the download is not active.
// |downloadId|: The id of the download to pause.
// |callback|: Called when the pause request is completed.
static void pause(long downloadId, optional NullCallback callback);
// Resume a paused download. If the request was successful the download is
- // in progress and unpaused. Otherwise
- // $ref:runtime.lastError
- // contains an error message. The request will fail if the download is not
- // active.
+ // in progress and unpaused. Otherwise $ref:runtime.lastError contains an
+ // error message. The request will fail if the download is not active.
// |downloadId|: The id of the download to resume.
// |callback|: Called when the resume request is completed.
static void resume(long downloadId, optional NullCallback callback);
@@ -392,16 +427,14 @@ namespace downloads {
static void cancel(long downloadId, optional NullCallback callback);
// Retrieve an icon for the specified download. For new downloads, file
- // icons are available after the $ref:onCreated
- // event has been received. The image returned by this function while a
- // download is in progress may be different from the image returned after
- // the download is complete. Icon retrieval is done by querying the
- // underlying operating system or toolkit depending on the platform. The
- // icon that is returned will therefore depend on a number of factors
- // including state of the download, platform, registered file types and
- // visual theme. If a file icon cannot be determined,
- // $ref:runtime.lastError
- // will contain an error message.
+ // icons are available after the $ref:onCreated event has been received. The
+ // image returned by this function while a download is in progress may be
+ // different from the image returned after the download is complete. Icon
+ // retrieval is done by querying the underlying operating system or toolkit
+ // depending on the platform. The icon that is returned will therefore
+ // depend on a number of factors including state of the download, platform,
+ // registered file types and visual theme. If a file icon cannot be
+ // determined, $ref:runtime.lastError will contain an error message.
// |downloadId|: The identifier for the download.
// |callback|: A URL to an image that represents the download.
static void getFileIcon(long downloadId,
@@ -409,7 +442,7 @@ namespace downloads {
GetFileIconCallback callback);
// Open the downloaded file now if the $ref:DownloadItem is complete;
- // returns an error through $ref:runtime.lastError otherwise. Requires the
+ // otherwise returns an error through $ref:runtime.lastError. Requires the
// <code>"downloads.open"</code> permission in addition to the
// <code>"downloads"</code> permission. An $ref:onChanged event will fire
// when the item is opened for the first time.
@@ -420,11 +453,19 @@ namespace downloads {
// |downloadId|: The identifier for the downloaded file.
static void show(long downloadId);
- // Erase matching $ref:DownloadItem from history. An $ref:onErased event
- // will fire for each $ref:DownloadItem that matches <code>query</code>,
- // then <code>callback</code> will be called.
+ // Show the default Downloads folder in a file manager.
+ static void showDefaultFolder();
+
+ // Erase matching $ref:DownloadItem from history without deleting the
+ // downloaded file. An $ref:onErased event will fire for each
+ // $ref:DownloadItem that matches <code>query</code>, then
+ // <code>callback</code> will be called.
static void erase(DownloadQuery query, optional EraseCallback callback);
+ // Remove the downloaded file if it exists and the $ref:DownloadItem is
+ // complete; otherwise return an error through $ref:runtime.lastError.
+ static void removeFile(long downloadId, optional NullCallback callback);
+
// Prompt the user to accept a dangerous download. Does not automatically
// accept dangerous downloads. If the download is accepted, then an
// $ref:onChanged event will fire, otherwise nothing will happen. When all
@@ -433,15 +474,17 @@ namespace downloads {
// renamed to the target filename, the |state| changes to 'complete', and
// $ref:onChanged fires.
// |downloadId|: The identifier for the $ref:DownloadItem.
- static void acceptDanger(long downloadId);
+ // |callback|: Called when the danger prompt dialog closes.
+ static void acceptDanger(long downloadId, optional NullCallback callback);
- // Initiate dragging the downloaded file to another application.
+ // Initiate dragging the downloaded file to another application. Call in a
+ // javascript <code>ondragstart</code> handler.
static void drag(long downloadId);
};
interface Events {
- // This event fires with the $ref:DownloadItem
- // object when a download begins.
+ // This event fires with the $ref:DownloadItem object when a download
+ // begins.
static void onCreated(DownloadItem downloadItem);
// Fires with the <code>downloadId</code> when a download is erased from
@@ -451,9 +494,9 @@ namespace downloads {
static void onErased(long downloadId);
// When any of a $ref:DownloadItem's properties except
- // <code>bytesReceived</code> changes, this event fires with the
- // <code>downloadId</code> and an object containing the properties that
- // changed.
+ // <code>bytesReceived</code> and <code>estimatedEndTime</code> changes,
+ // this event fires with the <code>downloadId</code> and an object
+ // containing the properties that changed.
static void onChanged(DownloadDelta downloadDelta);
// During the filename determination process, extensions will be given the
diff --git a/chrome/renderer/resources/extensions/downloads_custom_bindings.js b/chrome/renderer/resources/extensions/downloads_custom_bindings.js
index be7d1c4..88efbac 100644
--- a/chrome/renderer/resources/extensions/downloads_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/downloads_custom_bindings.js
@@ -16,19 +16,36 @@ eventBindings.registerArgumentMassager(
// Copy the id so that extensions can't change it.
var downloadId = downloadItem.id;
var suggestable = true;
+ function isValidResult(result) {
+ if (result === undefined)
+ return false;
+ if (typeof(result) != 'object') {
+ console.error('Error: Invocation of form suggest(' + typeof(result) +
+ ') doesn\'t match definition suggest({filename: string, ' +
+ 'conflictAction: string})');
+ return false;
+ } else if ((typeof(result.filename) != 'string') ||
+ (result.filename.length == 0)) {
+ console.error('Error: "filename" parameter to suggest() must be a ' +
+ 'non-empty string');
+ return false;
+ } else if ([undefined, 'uniquify', 'overwrite', 'prompt'].indexOf(
+ result.conflictAction) < 0) {
+ console.error('Error: "conflictAction" parameter to suggest() must be ' +
+ 'one of undefined, "uniquify", "overwrite", "prompt"');
+ return false;
+ }
+ return true;
+ }
function suggestCallback(result) {
if (!suggestable) {
console.error('suggestCallback may not be called more than once.');
return;
}
suggestable = false;
- if ((typeof(result) == 'object') &&
- result.filename &&
- (typeof(result.filename) == 'string') &&
- ((result.conflict_action == undefined) ||
- (typeof(result.conflict_action) == 'string'))) {
+ if (isValidResult(result)) {
downloadsInternal.determineFilename(
- downloadId, result.filename, result.conflict_action || "");
+ downloadId, result.filename, result.conflictAction || "");
} else {
downloadsInternal.determineFilename(downloadId, "", "");
}
diff --git a/content/browser/download/download_item_impl.cc b/content/browser/download/download_item_impl.cc
index 5471df2..b49a256 100644
--- a/content/browser/download/download_item_impl.cc
+++ b/content/browser/download/download_item_impl.cc
@@ -608,6 +608,20 @@ bool DownloadItemImpl::GetFileExternallyRemoved() const {
return file_externally_removed_;
}
+void DownloadItemImpl::DeleteFile() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if ((GetState() != DownloadItem::COMPLETE) ||
+ file_externally_removed_) {
+ return;
+ }
+ BrowserThread::PostTaskAndReply(
+ BrowserThread::FILE, FROM_HERE,
+ base::Bind(&DeleteDownloadedFile, current_path_),
+ base::Bind(&DownloadItemImpl::OnDownloadedFileRemoved,
+ weak_ptr_factory_.GetWeakPtr()));
+ current_path_.clear();
+}
+
bool DownloadItemImpl::IsDangerous() const {
#if defined(OS_WIN)
// TODO(noelutz): At this point only the windows views UI supports
diff --git a/content/browser/download/download_item_impl.h b/content/browser/download/download_item_impl.h
index b57bae0..ac260b1 100644
--- a/content/browser/download/download_item_impl.h
+++ b/content/browser/download/download_item_impl.h
@@ -127,6 +127,7 @@ class CONTENT_EXPORT DownloadItemImpl
virtual const std::string& GetHash() const OVERRIDE;
virtual const std::string& GetHashState() const OVERRIDE;
virtual bool GetFileExternallyRemoved() const OVERRIDE;
+ virtual void DeleteFile() OVERRIDE;
virtual bool IsDangerous() const OVERRIDE;
virtual DownloadDangerType GetDangerType() const OVERRIDE;
virtual bool TimeRemaining(base::TimeDelta* remaining) const OVERRIDE;
diff --git a/content/public/browser/download_item.h b/content/public/browser/download_item.h
index ec29c94..5ef9597 100644
--- a/content/public/browser/download_item.h
+++ b/content/public/browser/download_item.h
@@ -235,6 +235,12 @@ class CONTENT_EXPORT DownloadItem : public base::SupportsUserData {
// external action.
virtual bool GetFileExternallyRemoved() const = 0;
+ // If the file is successfully deleted, then GetFileExternallyRemoved() will
+ // become true and DownloadItem::OnDownloadUpdated() will be called. Does
+ // nothing if GetState() == COMPLETE or GetFileExternallyRemoved() is already
+ // true.
+ virtual void DeleteFile() = 0;
+
// True if the file that will be written by the download is dangerous
// and we will require a call to ValidateDangerousDownload() to complete.
// False if the download is safe or that function has been called.
diff --git a/content/public/test/mock_download_item.h b/content/public/test/mock_download_item.h
index 8578f5e..6dfbf02 100644
--- a/content/public/test/mock_download_item.h
+++ b/content/public/test/mock_download_item.h
@@ -60,6 +60,7 @@ class MockDownloadItem : public DownloadItem {
MOCK_CONST_METHOD0(GetHash, const std::string&());
MOCK_CONST_METHOD0(GetHashState, const std::string&());
MOCK_CONST_METHOD0(GetFileExternallyRemoved, bool());
+ MOCK_METHOD0(DeleteFile, void());
MOCK_CONST_METHOD0(IsDangerous, bool());
MOCK_CONST_METHOD0(GetDangerType, DownloadDangerType());
MOCK_CONST_METHOD1(TimeRemaining, bool(base::TimeDelta*));