Commit 2d290dc4 authored by msramek's avatar msramek Committed by Commit bot

Add a cache counter for iOS.

The CacheCounter class in chrome/browser/browsing_data could not be
componentized (unlike other counters in components/browsing_data) because
of its usage of StoragePartitionHttpCacheDataRemover that has a dependency
on content/.

This CL implements a separate CacheCounter class for iOS with the same
functionality. The core of the implementation, a series of IO thread
callbacks, is delegated to the private IOThreadCacheCounter class that
mimics StoragePartitionHttpCacheDataRemover.

BUG=620317

Review-Url: https://codereview.chromium.org/2354643002
Cr-Commit-Position: refs/heads/master@{#420599}
parent f2210407
...@@ -33,6 +33,7 @@ test("ios_chrome_unittests") { ...@@ -33,6 +33,7 @@ test("ios_chrome_unittests") {
sources = [ sources = [
"app/application_delegate/memory_warning_helper_unittest.mm", "app/application_delegate/memory_warning_helper_unittest.mm",
"app/safe_mode_util_unittest.cc", "app/safe_mode_util_unittest.cc",
"browser/browsing_data/cache_counter_unittest.cc",
"browser/chrome_url_util_unittest.mm", "browser/chrome_url_util_unittest.mm",
"browser/crash_loop_detection_util_unittest.mm", "browser/crash_loop_detection_util_unittest.mm",
"browser/favicon/large_icon_cache_unittest.cc", "browser/favicon/large_icon_cache_unittest.cc",
...@@ -95,6 +96,7 @@ test("ios_chrome_unittests") { ...@@ -95,6 +96,7 @@ test("ios_chrome_unittests") {
"//components/autofill/core/common:common", "//components/autofill/core/common:common",
"//components/autofill/ios/browser:browser", "//components/autofill/ios/browser:browser",
"//components/bookmarks/test", "//components/bookmarks/test",
"//components/browsing_data/core",
"//components/favicon_base", "//components/favicon_base",
"//components/metrics", "//components/metrics",
"//components/metrics:test_support", "//components/metrics:test_support",
...@@ -117,6 +119,7 @@ test("ios_chrome_unittests") { ...@@ -117,6 +119,7 @@ test("ios_chrome_unittests") {
"//ios/chrome/common", "//ios/chrome/common",
"//ios/chrome/test", "//ios/chrome/test",
"//ios/chrome/test:run_all_unittests", "//ios/chrome/test:run_all_unittests",
"//ios/net",
"//ios/public/provider/chrome/browser", "//ios/public/provider/chrome/browser",
"//ios/public/provider/chrome/browser:test_support", "//ios/public/provider/chrome/browser:test_support",
"//ios/testing:ocmock_support", "//ios/testing:ocmock_support",
......
...@@ -95,6 +95,8 @@ source_set("browser") { ...@@ -95,6 +95,8 @@ source_set("browser") {
"browsing_data/browsing_data_counter_wrapper.h", "browsing_data/browsing_data_counter_wrapper.h",
"browsing_data/browsing_data_remover_helper.cc", "browsing_data/browsing_data_remover_helper.cc",
"browsing_data/browsing_data_remover_helper.h", "browsing_data/browsing_data_remover_helper.h",
"browsing_data/cache_counter.cc",
"browsing_data/cache_counter.h",
"browsing_data/ios_browsing_data_counter_factory.cc", "browsing_data/ios_browsing_data_counter_factory.cc",
"browsing_data/ios_browsing_data_counter_factory.h", "browsing_data/ios_browsing_data_counter_factory.h",
"browsing_data/ios_chrome_browsing_data_remover.h", "browsing_data/ios_chrome_browsing_data_remover.h",
......
// 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 "base/bind.h"
#include "components/browsing_data/core/pref_names.h"
#include "ios/chrome/browser/browsing_data/cache_counter.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/web_thread.h"
#include "net/base/completion_callback.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_cache.h"
#include "net/http/http_transaction_factory.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
namespace {
class IOThreadCacheCounter {
public:
IOThreadCacheCounter(
const scoped_refptr<net::URLRequestContextGetter>& context_getter,
const net::CompletionCallback& result_callback)
: next_step_(STEP_GET_BACKEND),
context_getter_(context_getter),
result_callback_(result_callback),
result_(0),
backend_(nullptr) {}
void Count() {
web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
base::Bind(&IOThreadCacheCounter::CountInternal,
base::Unretained(this), net::OK));
}
private:
enum Step {
STEP_GET_BACKEND, // Get the disk_cache::Backend instance.
STEP_COUNT, // Run CalculateSizeOfAllEntries() on it.
STEP_CALLBACK, // Respond on the UI thread.
STEP_DONE // Calculation completed.
};
void CountInternal(int rv) {
DCHECK_CURRENTLY_ON(web::WebThread::IO);
while (rv != net::ERR_IO_PENDING && next_step_ != STEP_DONE) {
// In case of an error, skip to the last step.
if (rv < 0)
next_step_ = STEP_CALLBACK;
// Process the counting in three steps: STEP_GET_BACKEND -> STEP_COUNT ->
// -> STEP_CALLBACK.
switch (next_step_) {
case STEP_GET_BACKEND: {
next_step_ = STEP_COUNT;
net::HttpCache* http_cache = context_getter_->GetURLRequestContext()
->http_transaction_factory()
->GetCache();
rv = http_cache->GetBackend(
&backend_, base::Bind(&IOThreadCacheCounter::CountInternal,
base::Unretained(this)));
break;
}
case STEP_COUNT: {
next_step_ = STEP_CALLBACK;
DCHECK(backend_);
rv = backend_->CalculateSizeOfAllEntries(base::Bind(
&IOThreadCacheCounter::CountInternal, base::Unretained(this)));
break;
}
case STEP_CALLBACK: {
next_step_ = STEP_DONE;
result_ = rv;
web::WebThread::PostTask(
web::WebThread::UI, FROM_HERE,
base::Bind(&IOThreadCacheCounter::OnCountingFinished,
base::Unretained(this)));
break;
}
case STEP_DONE: {
NOTREACHED();
}
}
}
}
void OnCountingFinished() {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
result_callback_.Run(result_);
delete this;
}
Step next_step_;
scoped_refptr<net::URLRequestContextGetter> context_getter_;
net::CompletionCallback result_callback_;
int result_;
disk_cache::Backend* backend_;
};
} // namespace
CacheCounter::CacheCounter(web::BrowserState* browser_state)
: pending_(false), browser_state_(browser_state), weak_ptr_factory_(this) {}
CacheCounter::~CacheCounter() {}
const char* CacheCounter::GetPrefName() const {
return browsing_data::prefs::kDeleteCache;
}
void CacheCounter::Count() {
// TODO(msramek): disk_cache::Backend currently does not implement counting
// for subsets of cache, only for the entire cache. Thus, we ignore the time
// period setting and always request counting for the unbounded time interval.
// It is up to the UI to interpret the results for finite time intervals as
// upper estimates.
pending_ = true;
// IOThreadCacheCounter deletes itself when done.
(new IOThreadCacheCounter(browser_state_->GetRequestContext(),
base::Bind(&CacheCounter::OnCacheSizeCalculated,
weak_ptr_factory_.GetWeakPtr())))
->Count();
}
void CacheCounter::OnCacheSizeCalculated(int result_bytes) {
pending_ = false;
// A value less than 0 means a net error code.
if (result_bytes < 0)
return;
ReportResult(result_bytes);
}
// 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.
#ifndef IOS_CHROME_BROWSER_BROWSING_DATA_CACHE_COUNTER_H_
#define IOS_CHROME_BROWSER_BROWSING_DATA_CACHE_COUNTER_H_
#include "base/memory/weak_ptr.h"
#include "components/browsing_data/core/counters/browsing_data_counter.h"
namespace web {
class BrowserState;
}
class CacheCounter : public browsing_data::BrowsingDataCounter {
public:
explicit CacheCounter(web::BrowserState* browser_state);
~CacheCounter() override;
// Whether this counter awaits the calculation result callback.
// Used only for testing.
bool pending() { return pending_; }
// BrowsingDataCounter implementation.
const char* GetPrefName() const override;
private:
// BrowsingDataCounter implementation.
void Count() override;
void OnCacheSizeCalculated(int result_bytes);
bool pending_;
web::BrowserState* browser_state_;
base::WeakPtrFactory<CacheCounter> weak_ptr_factory_;
};
#endif // IOS_CHROME_BROWSER_BROWSING_DATA_CACHE_COUNTER_H_
// 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.
//
// Note that this file only tests the basic behavior of the cache counter, as in
// when it counts and when not, when result is nonzero and when not. It does not
// test whether the result of the counting is correct. This is the
// responsibility of a lower layer, and is tested in
// DiskCacheBackendTest.CalculateSizeOfAllEntries in net_unittests.
#include "ios/chrome/browser/browsing_data/cache_counter.h"
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/browsing_data/core/browsing_data_utils.h"
#include "components/browsing_data/core/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "ios/web/public/test/test_browser_state.h"
#include "ios/web/public/test/test_web_thread_bundle.h"
#include "ios/web/public/web_thread.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_cache.h"
#include "net/http/http_transaction_factory.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
class CacheCounterTest : public testing::Test {
public:
CacheCounterTest() {
prefs_.registry()->RegisterIntegerPref(
browsing_data::prefs::kDeleteTimePeriod,
static_cast<int>(browsing_data::ALL_TIME));
prefs_.registry()->RegisterBooleanPref(browsing_data::prefs::kDeleteCache,
true);
context_getter_ = browser_state_.GetRequestContext();
}
~CacheCounterTest() override {}
web::TestBrowserState* browser_state() { return &browser_state_; }
PrefService* prefs() { return &prefs_; }
void SetCacheDeletionPref(bool value) {
prefs()->SetBoolean(browsing_data::prefs::kDeleteCache, value);
}
void SetDeletionPeriodPref(browsing_data::TimePeriod period) {
prefs()->SetInteger(browsing_data::prefs::kDeleteTimePeriod,
static_cast<int>(period));
}
// Create a cache entry on the IO thread.
void CreateCacheEntry() {
current_operation_ = OPERATION_ADD_ENTRY;
next_step_ = STEP_GET_BACKEND;
web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
base::Bind(&CacheCounterTest::CacheOperationStep,
base::Unretained(this), net::OK));
WaitForIOThread();
}
// Clear the cache on the IO thread.
void ClearCache() {
current_operation_ = OPERATION_CLEAR_CACHE;
next_step_ = STEP_GET_BACKEND;
web::WebThread::PostTask(web::WebThread::IO, FROM_HERE,
base::Bind(&CacheCounterTest::CacheOperationStep,
base::Unretained(this), net::OK));
WaitForIOThread();
}
// Wait for IO thread operations, such as cache creation, counting, writing,
// deletion etc.
void WaitForIOThread() {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
run_loop_.reset(new base::RunLoop());
run_loop_->Run();
}
// A callback method to be used by counters to report the result.
void CountingCallback(
std::unique_ptr<browsing_data::BrowsingDataCounter::Result> result) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
finished_ = result->Finished();
if (finished_) {
result_ =
static_cast<browsing_data::BrowsingDataCounter::FinishedResult*>(
result.get())
->Value();
}
if (run_loop_ && finished_)
run_loop_->Quit();
}
// Get the last reported counter result.
browsing_data::BrowsingDataCounter::ResultInt GetResult() {
DCHECK(finished_);
return result_;
}
private:
enum CacheOperation {
OPERATION_ADD_ENTRY,
OPERATION_CLEAR_CACHE,
};
enum CacheEntryCreationStep {
STEP_GET_BACKEND,
STEP_CLEAR_CACHE,
STEP_CREATE_ENTRY,
STEP_WRITE_DATA,
STEP_CALLBACK,
STEP_DONE
};
// One step in the process of creating a cache entry or clearing the cache.
// Every step must be executed on IO thread after the previous one has
// finished.
void CacheOperationStep(int rv) {
while (rv != net::ERR_IO_PENDING && next_step_ != STEP_DONE) {
// The testing browser state uses a memory cache which should not cause
// any errors.
DCHECK_GE(rv, 0);
switch (next_step_) {
case STEP_GET_BACKEND: {
next_step_ = current_operation_ == OPERATION_ADD_ENTRY
? STEP_CREATE_ENTRY
: STEP_CLEAR_CACHE;
net::HttpCache* http_cache = context_getter_->GetURLRequestContext()
->http_transaction_factory()
->GetCache();
rv = http_cache->GetBackend(
&backend_, base::Bind(&CacheCounterTest::CacheOperationStep,
base::Unretained(this)));
break;
}
case STEP_CLEAR_CACHE: {
next_step_ = STEP_CALLBACK;
DCHECK(backend_);
rv = backend_->DoomAllEntries(base::Bind(
&CacheCounterTest::CacheOperationStep, base::Unretained(this)));
break;
}
case STEP_CREATE_ENTRY: {
next_step_ = STEP_WRITE_DATA;
DCHECK(backend_);
rv = backend_->CreateEntry(
"entry_key", &entry_,
base::Bind(&CacheCounterTest::CacheOperationStep,
base::Unretained(this)));
break;
}
case STEP_WRITE_DATA: {
next_step_ = STEP_CALLBACK;
std::string data = "entry data";
scoped_refptr<net::StringIOBuffer> buffer =
new net::StringIOBuffer(data);
rv = entry_->WriteData(
0, 0, buffer.get(), data.size(),
base::Bind(&CacheCounterTest::CacheOperationStep,
base::Unretained(this)),
true);
break;
}
case STEP_CALLBACK: {
next_step_ = STEP_DONE;
if (current_operation_ == OPERATION_ADD_ENTRY)
entry_->Close();
web::WebThread::PostTask(
web::WebThread::UI, FROM_HERE,
base::Bind(&CacheCounterTest::Callback, base::Unretained(this)));
break;
}
case STEP_DONE: {
NOTREACHED();
}
}
}
}
// General completion callback.
void Callback() {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
if (run_loop_)
run_loop_->Quit();
}
web::TestWebThreadBundle bundle_;
std::unique_ptr<base::RunLoop> run_loop_;
CacheOperation current_operation_;
CacheEntryCreationStep next_step_;
scoped_refptr<net::URLRequestContextGetter> context_getter_;
disk_cache::Backend* backend_;
disk_cache::Entry* entry_;
bool finished_;
browsing_data::BrowsingDataCounter::ResultInt result_;
web::TestBrowserState browser_state_;
TestingPrefServiceSimple prefs_;
};
// Tests that for the empty cache, the result is zero.
TEST_F(CacheCounterTest, Empty) {
CacheCounter counter(browser_state());
counter.Init(prefs(), base::Bind(&CacheCounterTest::CountingCallback,
base::Unretained(this)));
counter.Restart();
WaitForIOThread();
EXPECT_EQ(0u, GetResult());
}
// Tests that for a non-empty cache, the result is nonzero, and after deleting
// its contents, it's zero again. Note that the exact value of the result
// is tested in DiskCacheBackendTest.CalculateSizeOfAllEntries.
TEST_F(CacheCounterTest, BeforeAndAfterClearing) {
CreateCacheEntry();
CacheCounter counter(browser_state());
counter.Init(prefs(), base::Bind(&CacheCounterTest::CountingCallback,
base::Unretained(this)));
counter.Restart();
WaitForIOThread();
EXPECT_NE(0u, GetResult());
ClearCache();
counter.Restart();
WaitForIOThread();
EXPECT_EQ(0u, GetResult());
}
// Tests that the counter starts counting automatically when the deletion
// pref changes to true.
TEST_F(CacheCounterTest, PrefChanged) {
SetCacheDeletionPref(false);
CacheCounter counter(browser_state());
counter.Init(prefs(), base::Bind(&CacheCounterTest::CountingCallback,
base::Unretained(this)));
SetCacheDeletionPref(true);
WaitForIOThread();
EXPECT_EQ(0u, GetResult());
}
// Tests that the counter does not count if the deletion preference is false.
TEST_F(CacheCounterTest, PrefIsFalse) {
SetCacheDeletionPref(false);
CacheCounter counter(browser_state());
counter.Init(prefs(), base::Bind(&CacheCounterTest::CountingCallback,
base::Unretained(this)));
counter.Restart();
EXPECT_FALSE(counter.pending());
}
// Tests that the counting is restarted when the time period changes. Currently,
// the results should be the same for every period. This is because the counter
// always counts the size of the entire cache, and it is up to the UI
// to interpret it as exact value or upper bound.
TEST_F(CacheCounterTest, PeriodChanged) {
CreateCacheEntry();
CacheCounter counter(browser_state());
counter.Init(prefs(), base::Bind(&CacheCounterTest::CountingCallback,
base::Unretained(this)));
SetDeletionPeriodPref(browsing_data::LAST_HOUR);
WaitForIOThread();
browsing_data::BrowsingDataCounter::ResultInt result = GetResult();
SetDeletionPeriodPref(browsing_data::LAST_DAY);
WaitForIOThread();
EXPECT_EQ(result, GetResult());
SetDeletionPeriodPref(browsing_data::LAST_WEEK);
WaitForIOThread();
EXPECT_EQ(result, GetResult());
SetDeletionPeriodPref(browsing_data::FOUR_WEEKS);
WaitForIOThread();
EXPECT_EQ(result, GetResult());
SetDeletionPeriodPref(browsing_data::ALL_TIME);
WaitForIOThread();
EXPECT_EQ(result, GetResult());
}
} // namespace
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "components/browsing_data/core/pref_names.h" #include "components/browsing_data/core/pref_names.h"
#include "components/keyed_service/core/service_access_type.h" #include "components/keyed_service/core/service_access_type.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h" #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/browsing_data/cache_counter.h"
#include "ios/chrome/browser/experimental_flags.h" #include "ios/chrome/browser/experimental_flags.h"
#include "ios/chrome/browser/history/history_service_factory.h" #include "ios/chrome/browser/history/history_service_factory.h"
#include "ios/chrome/browser/history/web_history_service_factory.h" #include "ios/chrome/browser/history/web_history_service_factory.h"
...@@ -39,6 +40,9 @@ IOSBrowsingDataCounterFactory::GetForBrowserStateAndPref( ...@@ -39,6 +40,9 @@ IOSBrowsingDataCounterFactory::GetForBrowserStateAndPref(
IOSChromeProfileSyncServiceFactory::GetForBrowserState(browser_state)); IOSChromeProfileSyncServiceFactory::GetForBrowserState(browser_state));
} }
if (pref_name == browsing_data::prefs::kDeleteCache)
return base::MakeUnique<CacheCounter>(browser_state);
if (pref_name == browsing_data::prefs::kDeletePasswords) { if (pref_name == browsing_data::prefs::kDeletePasswords) {
return base::MakeUnique<browsing_data::PasswordsCounter>( return base::MakeUnique<browsing_data::PasswordsCounter>(
IOSChromePasswordStoreFactory::GetForBrowserState( IOSChromePasswordStoreFactory::GetForBrowserState(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment