| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/browsing_data/downloads_counter.h" |
| |
| #include <set> |
| |
| #include "base/files/file_path.h" |
| #include "base/guid.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/download/download_history.h" |
| #include "chrome/browser/download/download_service.h" |
| #include "chrome/browser/download/download_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "components/browsing_data/core/browsing_data_utils.h" |
| #include "components/browsing_data/core/pref_names.h" |
| #include "components/history/core/browser/download_row.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/download_manager.h" |
| |
| #if defined(ENABLE_EXTENSIONS) |
| #include "extensions/common/extension.h" |
| #endif |
| |
| namespace { |
| |
| class DownloadsCounterTest : public InProcessBrowserTest, |
| public DownloadHistory::Observer { |
| public: |
| void SetUpOnMainThread() override { |
| time_ = base::Time::Now(); |
| items_count_ = 0; |
| manager_ = |
| content::BrowserContext::GetDownloadManager(browser()->profile()); |
| history_ = |
| DownloadServiceFactory::GetForBrowserContext(browser()->profile())-> |
| GetDownloadHistory(); |
| history_->AddObserver(this); |
| |
| otr_manager_ = |
| content::BrowserContext::GetDownloadManager( |
| browser()->profile()->GetOffTheRecordProfile()); |
| SetDownloadsDeletionPref(true); |
| SetDeletionPeriodPref(browsing_data::ALL_TIME); |
| } |
| |
| void TearDownOnMainThread() override { |
| history_->RemoveObserver(this); |
| } |
| |
| // Adding and removing download items. --------------------------------------- |
| |
| std::string AddDownload() { |
| std::string guid = AddDownloadInternal( |
| content::DownloadItem::COMPLETE, |
| content::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE, |
| GURL(), |
| std::string(), |
| false); |
| guids_to_add_.insert(guid); |
| return guid; |
| } |
| |
| std::string AddIncognitoDownload() { |
| // Incognito downloads are not expected to be persisted. We don't need to |
| // wait for a callback from them, so we don't add them to |guids_to_add_|. |
| return AddDownloadInternal( |
| content::DownloadItem::COMPLETE, |
| content::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE, |
| GURL(), |
| std::string(), |
| true); |
| } |
| |
| #if defined(ENABLE_EXTENSIONS) |
| std::string AddExtensionDownload() { |
| // Extension downloads are not expected to be persisted. We don't need to |
| // wait for a callback from them, so we don't add them to |guids_to_add_|. |
| return AddDownloadInternal( |
| content::DownloadItem::COMPLETE, |
| content::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE, |
| GURL(), |
| extensions::Extension::kMimeType, |
| false); |
| } |
| |
| std::string AddUserScriptDownload() { |
| // User script downloads are not expected to be persisted. We don't need to |
| // wait for a callback from them, so we don't add them to |guids_to_add_|. |
| return AddDownloadInternal( |
| content::DownloadItem::COMPLETE, |
| content::DownloadDangerType::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE, |
| GURL("file:///download.user.js"), |
| "text/javascript", |
| false); |
| } |
| #endif |
| |
| std::string AddDownloadWithProperties( |
| content::DownloadItem::DownloadState state, |
| content::DownloadDangerType danger, |
| content::DownloadInterruptReason reason) { |
| std::string guid = AddDownloadInternal( |
| state, danger, reason, GURL(), std::string(), false); |
| guids_to_add_.insert(guid); |
| return guid; |
| } |
| |
| std::string AddDownloadInternal( |
| content::DownloadItem::DownloadState state, |
| content::DownloadDangerType danger, |
| content::DownloadInterruptReason reason, |
| const GURL& url, |
| std::string mime_type, |
| bool incognito) { |
| std::string guid = base::ToUpperASCII(base::GenerateGUID()); |
| |
| std::vector<GURL> url_chain; |
| url_chain.push_back(url); |
| |
| content::DownloadManager* manager = incognito ? otr_manager_ : manager_; |
| manager->CreateDownloadItem( |
| guid, |
| content::DownloadItem::kInvalidId + (++items_count_), |
| base::FilePath(FILE_PATH_LITERAL("current/path")), |
| base::FilePath(FILE_PATH_LITERAL("target/path")), |
| url_chain, |
| GURL(), |
| GURL(), |
| GURL(), |
| GURL(), |
| mime_type, |
| std::string(), |
| time_, |
| time_, |
| std::string(), |
| std::string(), |
| 1, |
| 1, |
| std::string(), |
| state, |
| danger, |
| reason, |
| false); |
| |
| return guid; |
| } |
| |
| void RemoveDownload(const std::string& guid) { |
| content::DownloadItem* item = manager_->GetDownloadByGuid(guid); |
| ids_to_remove_.insert(item->GetId()); |
| item->Remove(); |
| } |
| |
| // Miscellaneous. ------------------------------------------------------------ |
| |
| void SetDownloadsDeletionPref(bool value) { |
| browser()->profile()->GetPrefs()->SetBoolean( |
| browsing_data::prefs::kDeleteDownloadHistory, value); |
| } |
| |
| void SetDeletionPeriodPref(browsing_data::TimePeriod period) { |
| browser()->profile()->GetPrefs()->SetInteger( |
| browsing_data::prefs::kDeleteTimePeriod, static_cast<int>(period)); |
| } |
| |
| void RevertTimeInHours(int days) { |
| time_ -= base::TimeDelta::FromHours(days); |
| } |
| |
| // Waiting for downloads to be stored. --------------------------------------- |
| |
| void OnDownloadStored( |
| content::DownloadItem* item, |
| const history::DownloadRow& info) override { |
| // Ignore any updates on items that we have already processed. |
| if (guids_to_add_.find(item->GetGuid()) == guids_to_add_.end()) |
| return; |
| |
| // DownloadHistory updates us before the item is actually written on |
| // the history thread. Ignore this and wait until the item is actually |
| // persisted. |
| if (!DownloadHistory::IsPersisted(item)) |
| return; |
| |
| guids_to_add_.erase(item->GetGuid()); |
| |
| if (run_loop_ && guids_to_add_.empty()) |
| run_loop_->Quit(); |
| } |
| |
| void OnDownloadsRemoved(const DownloadHistory::IdSet& ids) override { |
| for (uint32_t id : ids) |
| ASSERT_EQ(1u, ids_to_remove_.erase(id)); |
| |
| if (run_loop_ && ids_to_remove_.empty()) |
| run_loop_->Quit(); |
| } |
| |
| void WaitForDownloadHistory() { |
| if (guids_to_add_.empty() && ids_to_remove_.empty()) |
| return; |
| |
| DCHECK(!run_loop_ || !run_loop_->running()); |
| run_loop_.reset(new base::RunLoop()); |
| run_loop_->Run(); |
| } |
| |
| // Retrieving the result. ---------------------------------------------------- |
| |
| browsing_data::BrowsingDataCounter::ResultInt GetResult() { |
| DCHECK(finished_); |
| return result_; |
| } |
| |
| void ResultCallback( |
| std::unique_ptr<browsing_data::BrowsingDataCounter::Result> result) { |
| finished_ = result->Finished(); |
| |
| if (finished_) { |
| result_ = |
| static_cast<browsing_data::BrowsingDataCounter::FinishedResult*>( |
| result.get()) |
| ->Value(); |
| } |
| } |
| |
| private: |
| std::unique_ptr<base::RunLoop> run_loop_; |
| |
| // GUIDs of download items that were added and for which we expect |
| // the OnDownloadStored() callback to be called. |
| std::set<std::string> guids_to_add_; |
| |
| // IDs of download items that are being removed from the download service |
| // and for which we expect the OnDownloadsRemoved() callback. Unlike in |
| // |guids_to_add_|, we don't store GUIDs, because OnDownloadsRemoved() returns |
| // a set of IDs. |
| std::set<uint32_t> ids_to_remove_; |
| |
| content::DownloadManager* manager_; |
| content::DownloadManager* otr_manager_; |
| DownloadHistory* history_; |
| base::Time time_; |
| |
| int items_count_; |
| |
| bool finished_; |
| browsing_data::BrowsingDataCounter::ResultInt result_; |
| }; |
| |
| // Tests that we count the total number of downloads correctly. |
| IN_PROC_BROWSER_TEST_F(DownloadsCounterTest, Count) { |
| Profile* profile = browser()->profile(); |
| DownloadsCounter counter(profile); |
| counter.Init(profile->GetPrefs(), |
| base::Bind(&DownloadsCounterTest::ResultCallback, |
| base::Unretained(this))); |
| counter.Restart(); |
| EXPECT_EQ(0u, GetResult()); |
| |
| std::string first_download = AddDownload(); |
| AddDownload(); |
| std::string last_download = AddDownload(); |
| WaitForDownloadHistory(); |
| counter.Restart(); |
| EXPECT_EQ(3, GetResult()); |
| |
| RemoveDownload(last_download); |
| RemoveDownload(first_download); |
| WaitForDownloadHistory(); |
| counter.Restart(); |
| EXPECT_EQ(1, GetResult()); |
| |
| AddDownload(); |
| WaitForDownloadHistory(); |
| counter.Restart(); |
| EXPECT_EQ(2, GetResult()); |
| } |
| |
| // Tests that not just standard complete downloads are counted. |
| IN_PROC_BROWSER_TEST_F(DownloadsCounterTest, Types) { |
| Profile* profile = browser()->profile(); |
| DownloadsCounter counter(profile); |
| counter.Init(profile->GetPrefs(), |
| base::Bind(&DownloadsCounterTest::ResultCallback, |
| base::Unretained(this))); |
| |
| AddDownload(); |
| AddDownloadWithProperties( |
| content::DownloadItem::COMPLETE, |
| content::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE); |
| AddDownloadWithProperties( |
| content::DownloadItem::COMPLETE, |
| content::DOWNLOAD_DANGER_TYPE_USER_VALIDATED, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE); |
| AddDownloadWithProperties( |
| content::DownloadItem::CANCELLED, |
| content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, |
| content::DOWNLOAD_INTERRUPT_REASON_NONE); |
| AddDownloadWithProperties( |
| content::DownloadItem::INTERRUPTED, |
| content::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, |
| content::DOWNLOAD_INTERRUPT_REASON_FILE_FAILED); |
| AddDownloadWithProperties( |
| content::DownloadItem::INTERRUPTED, |
| content::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT, |
| content::DOWNLOAD_INTERRUPT_REASON_USER_CANCELED); |
| |
| WaitForDownloadHistory(); |
| counter.Restart(); |
| EXPECT_EQ(6u, GetResult()); |
| } |
| |
| // Tests that downloads not persisted by DownloadHistory are not counted. |
| IN_PROC_BROWSER_TEST_F(DownloadsCounterTest, NotPersisted) { |
| Profile* profile = browser()->profile(); |
| DownloadsCounter counter(profile); |
| counter.Init(profile->GetPrefs(), |
| base::Bind(&DownloadsCounterTest::ResultCallback, |
| base::Unretained(this))); |
| |
| // Extension and user scripts download are not persisted. |
| AddDownload(); |
| #if defined(ENABLE_EXTENSIONS) |
| AddUserScriptDownload(); |
| AddExtensionDownload(); |
| #endif |
| |
| WaitForDownloadHistory(); |
| counter.Restart(); |
| EXPECT_EQ(1u, GetResult()); |
| |
| // Neither are downloads in incognito mode. |
| AddIncognitoDownload(); |
| |
| WaitForDownloadHistory(); |
| counter.Restart(); |
| EXPECT_EQ(1u, GetResult()); |
| } |
| |
| // Tests that the counter takes time ranges into account. |
| IN_PROC_BROWSER_TEST_F(DownloadsCounterTest, TimeRanges) { |
| AddDownload(); |
| AddDownload(); // 2 items |
| |
| RevertTimeInHours(12); |
| AddDownload(); |
| AddDownload(); |
| AddDownload(); // 5 items |
| |
| RevertTimeInHours(2 * 24); |
| AddDownload(); |
| AddDownload(); // 7 items |
| |
| RevertTimeInHours(10 * 24); |
| AddDownload(); // 8 items |
| |
| RevertTimeInHours(30 * 24); |
| AddDownload(); |
| AddDownload(); |
| AddDownload(); // 11 items |
| |
| WaitForDownloadHistory(); |
| |
| Profile* profile = browser()->profile(); |
| DownloadsCounter counter(profile); |
| counter.Init(profile->GetPrefs(), |
| base::Bind(&DownloadsCounterTest::ResultCallback, |
| base::Unretained(this))); |
| |
| SetDeletionPeriodPref(browsing_data::LAST_HOUR); |
| EXPECT_EQ(2u, GetResult()); |
| |
| SetDeletionPeriodPref(browsing_data::LAST_DAY); |
| EXPECT_EQ(5u, GetResult()); |
| |
| SetDeletionPeriodPref(browsing_data::LAST_WEEK); |
| EXPECT_EQ(7u, GetResult()); |
| |
| SetDeletionPeriodPref(browsing_data::FOUR_WEEKS); |
| EXPECT_EQ(8u, GetResult()); |
| |
| SetDeletionPeriodPref(browsing_data::ALL_TIME); |
| EXPECT_EQ(11u, GetResult()); |
| } |
| |
| } // namespace |