| // Copyright (c) 2015 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 <algorithm> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/platform_thread.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/content/conditional_cache_deletion_helper.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/http/http_cache.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| |
| using browsing_data::ConditionalCacheDeletionHelper; |
| using content::BrowserThread; |
| |
| namespace { |
| |
| bool KeyIsEven(const disk_cache::Entry* entry) { |
| int key_as_int = 0; |
| base::StringToInt(entry->GetKey().c_str(), &key_as_int); |
| return (key_as_int % 2) == 0; |
| } |
| |
| bool HasHttpsExampleOrigin(const GURL& url) { |
| return url.GetOrigin() == GURL("https://example.com"); |
| } |
| |
| } // namespace |
| |
| class ConditionalCacheDeletionHelperBrowserTest : public InProcessBrowserTest { |
| public: |
| // Initialization ------------------------------------------------------------ |
| |
| void SetUpOnMainThread() override { |
| // Prepare the commonly used callbacks. |
| done_callback_ = base::Bind( |
| &ConditionalCacheDeletionHelperBrowserTest::DoneCallback, |
| base::Unretained(this)); |
| |
| // UI and IO thread synchronization. |
| waitable_event_ = base::MakeUnique<base::WaitableEvent>( |
| base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| |
| // Get the storage partition. |
| partition_ = content::BrowserContext::GetDefaultStoragePartition( |
| browser()->profile()); |
| |
| // Get the cache backends. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::SetUpOnIOThread, |
| base::Unretained(this))); |
| WaitForTasksOnIOThread(); |
| } |
| |
| void SetUpOnIOThread() { |
| net::URLRequestContextGetter* context = partition_->GetURLRequestContext(); |
| |
| net::HttpCache* cache = context->GetURLRequestContext()-> |
| http_transaction_factory()->GetCache(); |
| |
| SetNumberOfWaitedTasks(1); |
| WaitForCompletion(cache->GetBackend(&backend_, done_callback_)); |
| } |
| |
| void TearDownOnMainThread() override { |
| // The cache iterator must be deleted on the thread where it was created, |
| // which is the IO thread. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind( |
| &ConditionalCacheDeletionHelperBrowserTest::TearDownOnIOThread, |
| base::Unretained(this))); |
| WaitForTasksOnIOThread(); |
| } |
| |
| void TearDownOnIOThread() { |
| iterator_.reset(); |
| DoneCallback(net::OK); |
| } |
| |
| // Waiting for tasks to be done on IO thread. -------------------------------- |
| |
| void WaitForTasksOnIOThread() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| waitable_event_->Wait(); |
| } |
| |
| void SetNumberOfWaitedTasks(int count) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| remaining_tasks_ = count; |
| } |
| |
| void WaitForCompletion(int value) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (value >= 0) { |
| // We got the result immediately. |
| DoneCallback(value); |
| } else if (value == net::ERR_IO_PENDING) { |
| // We need to wait for the callback. |
| } else { |
| // An error has occurred. |
| NOTREACHED(); |
| } |
| } |
| |
| void DoneCallback(int value) { |
| DCHECK_GE(value, 0); // Negative values represent an error. |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (--remaining_tasks_ > 0) |
| return; |
| |
| waitable_event_->Signal(); |
| } |
| |
| // Cache operation shorthands. ----------------------------------------------- |
| |
| void CreateCacheEntries(const std::set<std::string>& keys) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| entries_.resize(keys.size()); |
| SetNumberOfWaitedTasks(keys.size()); |
| |
| int pos = 0; |
| for (const std::string& key : keys) { |
| WaitForCompletion(backend_->CreateEntry( |
| key, &entries_[pos++], done_callback_)); |
| } |
| } |
| |
| void DeleteEntries( |
| const base::Callback<bool(const disk_cache::Entry*)>& condition) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| ConditionalCacheDeletionHelper* helper = new ConditionalCacheDeletionHelper( |
| backend_, |
| condition); |
| |
| WaitForCompletion(helper->DeleteAndDestroySelfWhenFinished(done_callback_)); |
| } |
| |
| void GetRemainingKeys() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| current_entry_ = nullptr; |
| iterator_ = backend_->CreateIterator(); |
| GetNextKey(net::OK); |
| } |
| |
| void GetNextKey(int error) { |
| while (error != net::ERR_IO_PENDING) { |
| if (error == net::ERR_FAILED) { |
| DoneCallback(net::OK); |
| return; |
| } |
| |
| if (current_entry_) { |
| remaining_keys_.push_back(current_entry_->GetKey()); |
| } |
| |
| error = iterator_->OpenNextEntry( |
| ¤t_entry_, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::GetNextKey, |
| base::Unretained(this))); |
| } |
| } |
| |
| void CompareRemainingKeys(std::set<std::string> expected_set) { |
| std::vector<std::string> expected; |
| expected.assign(expected_set.begin(), expected_set.end()); |
| std::sort(remaining_keys_.begin(), remaining_keys_.end()); |
| EXPECT_EQ(expected, remaining_keys_); |
| } |
| |
| // Miscellaneous. ------------------------------------------------------------ |
| |
| private: |
| content::StoragePartition* partition_; |
| disk_cache::Backend* backend_ = nullptr; |
| std::unique_ptr<disk_cache::Backend::Iterator> iterator_; |
| disk_cache::Entry* current_entry_; |
| std::vector<disk_cache::Entry*> entries_; |
| |
| base::Callback<void(int)> done_callback_; |
| |
| std::unique_ptr<base::WaitableEvent> waitable_event_; |
| int remaining_tasks_; |
| |
| std::vector<std::string> remaining_keys_; |
| }; |
| |
| // Tests that ConditionalCacheDeletionHelper only deletes those cache entries |
| // that match the condition. |
| IN_PROC_BROWSER_TEST_F(ConditionalCacheDeletionHelperBrowserTest, Condition) { |
| // Create 5 entries. |
| std::set<std::string> keys; |
| keys.insert("123"); |
| keys.insert("47"); |
| keys.insert("56"); |
| keys.insert("81"); |
| keys.insert("42"); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::CreateCacheEntries, |
| base::Unretained(this), |
| base::ConstRef(keys))); |
| WaitForTasksOnIOThread(); |
| |
| // Delete the entries whose keys are even numbers. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::DeleteEntries, |
| base::Unretained(this), |
| base::Bind(&KeyIsEven))); |
| WaitForTasksOnIOThread(); |
| |
| // Expect that the keys with values 56 and 42 were deleted. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::GetRemainingKeys, |
| base::Unretained(this))); |
| WaitForTasksOnIOThread(); |
| |
| keys.erase("56"); |
| keys.erase("42"); |
| CompareRemainingKeys(keys); |
| } |
| |
| // Tests that ConditionalCacheDeletionHelper correctly constructs a condition |
| // for time and URL. |
| // |
| // Note: This test depends on the timing in cache backends and can be flaky |
| // if those backends are slow. If this turns out to be a problem, consider |
| // increasing the |timeout_ms| constant. |
| // |
| // Flakily timing out on Mac 10.11: https://crbug.com/646119 |
| #if defined(OS_MACOSX) |
| #define MAYBE_TimeAndURL DISABLED_TimeAndURL |
| #else |
| #define MAYBE_TimeAndURL TimeAndURL |
| #endif |
| IN_PROC_BROWSER_TEST_F(ConditionalCacheDeletionHelperBrowserTest, |
| MAYBE_TimeAndURL) { |
| const int64_t timeout_ms = 1; |
| |
| // Create some entries. |
| std::set<std::string> keys; |
| keys.insert("https://google.com/index.html"); |
| keys.insert("https://example.com/foo/bar/icon.png"); |
| keys.insert("http://chrome.com"); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::CreateCacheEntries, |
| base::Unretained(this), |
| base::ConstRef(keys))); |
| WaitForTasksOnIOThread(); |
| |
| // Wait |timeout_ms| milliseconds for the cache to write the entries. |
| // This assures that future entries will have timestamps strictly greater than |
| // the ones we just added. |
| base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(timeout_ms)); |
| base::Time now = base::Time::Now(); |
| |
| // Create a few more entries with a later timestamp. |
| std::set<std::string> newer_keys; |
| newer_keys.insert("https://google.com/"); |
| newer_keys.insert("https://example.com/foo/bar/icon2.png"); |
| newer_keys.insert("https://example.com/foo/bar/icon3.png"); |
| newer_keys.insert("http://example.com/foo/bar/icon4.png"); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::CreateCacheEntries, |
| base::Unretained(this), |
| base::ConstRef(newer_keys))); |
| WaitForTasksOnIOThread(); |
| |
| // Create a condition for entries with the "https://example.com" origin |
| // created after waiting. |
| base::Callback<bool(const disk_cache::Entry*)> condition = |
| ConditionalCacheDeletionHelper::CreateURLAndTimeCondition( |
| base::Bind(&HasHttpsExampleOrigin), now, base::Time::Max()); |
| |
| // Delete the entries. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::DeleteEntries, |
| base::Unretained(this), |
| base::ConstRef(condition))); |
| WaitForTasksOnIOThread(); |
| |
| // Expect that only "icon2.png" and "icon3.png" were deleted. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ConditionalCacheDeletionHelperBrowserTest::GetRemainingKeys, |
| base::Unretained(this))); |
| WaitForTasksOnIOThread(); |
| |
| keys.insert(newer_keys.begin(), newer_keys.end()); |
| keys.erase("https://example.com/foo/bar/icon2.png"); |
| keys.erase("https://example.com/foo/bar/icon3.png"); |
| CompareRemainingKeys(keys); |
| } |