diff options
author | tzik@chromium.org <tzik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-30 05:12:39 +0000 |
---|---|---|
committer | tzik@chromium.org <tzik@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-05-30 05:12:39 +0000 |
commit | 7660ec943dbb6c73943b71e7f0675add48604bd2 (patch) | |
tree | 174a91440979e73b7f6f8675f8b131417729ab44 /webkit/browser | |
parent | 6961c1c9fbc14d3d7b646d3f5b92331b60336d8b (diff) | |
download | chromium_src-7660ec943dbb6c73943b71e7f0675add48604bd2.zip chromium_src-7660ec943dbb6c73943b71e7f0675add48604bd2.tar.gz chromium_src-7660ec943dbb6c73943b71e7f0675add48604bd2.tar.bz2 |
Move webkit/quota files to webkit/browser/quota or webkit/common/quota
This CL includes:
- Move webkit/quota/quota_{types.{h,cc},callbacks.h} webkit/common/quota/,
- Move other webkit/quota/ files to webkit/browser/quota/,
- #include and include guard fix for the move,
- Split webkit/quota/webkit_quota.gypi to webkit/{browser,common}/quota,
- Change DEPS to allow #include webkit/common from chrome/browser.
BUG=244363
TEST=should build successfully and should pass deps check.
NOTRY=True
Review URL: https://chromiumcodereview.appspot.com/16010007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@203082 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'webkit/browser')
61 files changed, 9176 insertions, 48 deletions
diff --git a/webkit/browser/chromeos/fileapi/cros_mount_point_provider.h b/webkit/browser/chromeos/fileapi/cros_mount_point_provider.h index 4058312..0e88161 100644 --- a/webkit/browser/chromeos/fileapi/cros_mount_point_provider.h +++ b/webkit/browser/chromeos/fileapi/cros_mount_point_provider.h @@ -14,8 +14,8 @@ #include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "webkit/browser/fileapi/file_system_mount_point_provider.h" +#include "webkit/browser/quota/special_storage_policy.h" #include "webkit/common/fileapi/file_system_types.h" -#include "webkit/quota/special_storage_policy.h" #include "webkit/storage/webkit_storage_export.h" namespace fileapi { diff --git a/webkit/browser/chromeos/fileapi/cros_mount_point_provider_unittest.cc b/webkit/browser/chromeos/fileapi/cros_mount_point_provider_unittest.cc index 9eadd7c..a0c85a2 100644 --- a/webkit/browser/chromeos/fileapi/cros_mount_point_provider_unittest.cc +++ b/webkit/browser/chromeos/fileapi/cros_mount_point_provider_unittest.cc @@ -14,7 +14,7 @@ #include "webkit/browser/fileapi/file_permission_policy.h" #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/isolated_context.h" -#include "webkit/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" #define FPL(x) FILE_PATH_LITERAL(x) diff --git a/webkit/browser/database/database_quota_client.h b/webkit/browser/database/database_quota_client.h index c90c0d5b..69ba10d 100644 --- a/webkit/browser/database/database_quota_client.h +++ b/webkit/browser/database/database_quota_client.h @@ -10,8 +10,8 @@ #include "base/memory/ref_counted.h" #include "base/message_loop_proxy.h" -#include "webkit/quota/quota_client.h" -#include "webkit/quota/quota_types.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/common/quota/quota_types.h" #include "webkit/storage/webkit_storage_export.h" namespace webkit_database { diff --git a/webkit/browser/database/database_tracker.cc b/webkit/browser/database/database_tracker.cc index effe322..0866feb 100644 --- a/webkit/browser/database/database_tracker.cc +++ b/webkit/browser/database/database_tracker.cc @@ -23,8 +23,8 @@ #include "webkit/browser/database/database_quota_client.h" #include "webkit/browser/database/database_util.h" #include "webkit/browser/database/databases_table.h" -#include "webkit/quota/quota_manager.h" -#include "webkit/quota/special_storage_policy.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/special_storage_policy.h" namespace webkit_database { diff --git a/webkit/browser/database/database_tracker_unittest.cc b/webkit/browser/database/database_tracker_unittest.cc index 32bf570..f2b3a90 100644 --- a/webkit/browser/database/database_tracker_unittest.cc +++ b/webkit/browser/database/database_tracker_unittest.cc @@ -17,8 +17,8 @@ #include "third_party/sqlite/sqlite3.h" #include "webkit/base/origin_url_conversions.h" #include "webkit/browser/database/database_tracker.h" -#include "webkit/quota/mock_special_storage_policy.h" -#include "webkit/quota/quota_manager.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/quota_manager.h" namespace { diff --git a/webkit/browser/fileapi/async_file_test_helper.cc b/webkit/browser/fileapi/async_file_test_helper.cc index 796d411..7437293 100644 --- a/webkit/browser/fileapi/async_file_test_helper.cc +++ b/webkit/browser/fileapi/async_file_test_helper.cc @@ -9,8 +9,8 @@ #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/file_system_mount_point_provider.h" #include "webkit/browser/fileapi/file_system_url.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" namespace fileapi { diff --git a/webkit/browser/fileapi/async_file_test_helper.h b/webkit/browser/fileapi/async_file_test_helper.h index b77a856..062634d 100644 --- a/webkit/browser/fileapi/async_file_test_helper.h +++ b/webkit/browser/fileapi/async_file_test_helper.h @@ -8,7 +8,7 @@ #include "base/basictypes.h" #include "webkit/browser/fileapi/file_system_operation.h" #include "webkit/common/fileapi/file_system_types.h" -#include "webkit/quota/quota_status_code.h" +#include "webkit/common/quota/quota_status_code.h" namespace quota { class QuotaManager; diff --git a/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc b/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc index 2be01c8..92b9d04 100644 --- a/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc +++ b/webkit/browser/fileapi/copy_or_move_file_validator_unittest.cc @@ -17,8 +17,8 @@ #include "webkit/browser/fileapi/isolated_context.h" #include "webkit/browser/fileapi/mock_file_system_context.h" #include "webkit/browser/fileapi/test_mount_point_provider.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/mock_special_storage_policy.h" namespace fileapi { diff --git a/webkit/browser/fileapi/file_system_context.cc b/webkit/browser/fileapi/file_system_context.cc index 676d251..c8bb838 100644 --- a/webkit/browser/fileapi/file_system_context.cc +++ b/webkit/browser/fileapi/file_system_context.cc @@ -26,9 +26,9 @@ #include "webkit/browser/fileapi/syncable/local_file_sync_context.h" #include "webkit/browser/fileapi/syncable/syncable_file_system_util.h" #include "webkit/browser/fileapi/test_mount_point_provider.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/special_storage_policy.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" -#include "webkit/quota/special_storage_policy.h" #if defined(OS_CHROMEOS) #include "webkit/browser/chromeos/fileapi/cros_mount_point_provider.h" diff --git a/webkit/browser/fileapi/file_system_context_unittest.cc b/webkit/browser/fileapi/file_system_context_unittest.cc index 18ead11..c699fd2 100644 --- a/webkit/browser/fileapi/file_system_context_unittest.cc +++ b/webkit/browser/fileapi/file_system_context_unittest.cc @@ -13,8 +13,8 @@ #include "webkit/browser/fileapi/file_system_task_runners.h" #include "webkit/browser/fileapi/isolated_context.h" #include "webkit/browser/fileapi/mock_file_system_options.h" -#include "webkit/quota/mock_quota_manager.h" -#include "webkit/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" #define FPL(x) FILE_PATH_LITERAL(x) diff --git a/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc b/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc index e031b9a..f8cc7de 100644 --- a/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc +++ b/webkit/browser/fileapi/file_system_dir_url_request_job_unittest.cc @@ -28,7 +28,7 @@ #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/mock_file_system_context.h" #include "webkit/browser/fileapi/sandbox_mount_point_provider.h" -#include "webkit/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" namespace fileapi { namespace { diff --git a/webkit/browser/fileapi/file_system_operation_context.h b/webkit/browser/fileapi/file_system_operation_context.h index 752d8d7..32bcb23 100644 --- a/webkit/browser/fileapi/file_system_operation_context.h +++ b/webkit/browser/fileapi/file_system_operation_context.h @@ -9,7 +9,7 @@ #include "base/supports_user_data.h" #include "base/threading/thread_checker.h" #include "webkit/browser/fileapi/task_runner_bound_observer_list.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" #include "webkit/storage/webkit_storage_export.h" namespace base { diff --git a/webkit/browser/fileapi/file_system_quota_client.h b/webkit/browser/fileapi/file_system_quota_client.h index 25c7752..7154b41 100644 --- a/webkit/browser/fileapi/file_system_quota_client.h +++ b/webkit/browser/fileapi/file_system_quota_client.h @@ -14,8 +14,8 @@ #include "base/files/file_path.h" #include "base/memory/ref_counted.h" #include "webkit/browser/fileapi/file_system_quota_util.h" +#include "webkit/browser/quota/quota_client.h" #include "webkit/common/fileapi/file_system_types.h" -#include "webkit/quota/quota_client.h" #include "webkit/storage/webkit_storage_export.h" namespace base { diff --git a/webkit/browser/fileapi/file_system_quota_client_unittest.cc b/webkit/browser/fileapi/file_system_quota_client_unittest.cc index 2f7bb39..ade671a 100644 --- a/webkit/browser/fileapi/file_system_quota_client_unittest.cc +++ b/webkit/browser/fileapi/file_system_quota_client_unittest.cc @@ -20,7 +20,7 @@ #include "webkit/browser/fileapi/sandbox_mount_point_provider.h" #include "webkit/common/fileapi/file_system_types.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" namespace fileapi { namespace { diff --git a/webkit/browser/fileapi/local_file_system_cross_operation_unittest.cc b/webkit/browser/fileapi/local_file_system_cross_operation_unittest.cc index 7a31908..1f1ad04 100644 --- a/webkit/browser/fileapi/local_file_system_cross_operation_unittest.cc +++ b/webkit/browser/fileapi/local_file_system_cross_operation_unittest.cc @@ -19,9 +19,9 @@ #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/mock_file_system_context.h" #include "webkit/browser/fileapi/test_file_set.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/mock_quota_manager.h" -#include "webkit/quota/quota_manager.h" namespace fileapi { diff --git a/webkit/browser/fileapi/local_file_system_operation.cc b/webkit/browser/fileapi/local_file_system_operation.cc index a85316b..765be5c 100644 --- a/webkit/browser/fileapi/local_file_system_operation.cc +++ b/webkit/browser/fileapi/local_file_system_operation.cc @@ -22,11 +22,11 @@ #include "webkit/browser/fileapi/file_writer_delegate.h" #include "webkit/browser/fileapi/remove_operation_delegate.h" #include "webkit/browser/fileapi/sandbox_file_stream_writer.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/blob/shareable_file_reference.h" #include "webkit/common/fileapi/file_system_types.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" using webkit_blob::ScopedFile; using webkit_blob::ShareableFileReference; diff --git a/webkit/browser/fileapi/local_file_system_operation.h b/webkit/browser/fileapi/local_file_system_operation.h index ee5fe8d..9321c44 100644 --- a/webkit/browser/fileapi/local_file_system_operation.h +++ b/webkit/browser/fileapi/local_file_system_operation.h @@ -13,7 +13,7 @@ #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/file_writer_delegate.h" #include "webkit/common/blob/scoped_file.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" #include "webkit/storage/webkit_storage_export.h" namespace chromeos { diff --git a/webkit/browser/fileapi/local_file_system_operation_unittest.cc b/webkit/browser/fileapi/local_file_system_operation_unittest.cc index fec56e3..19a2467 100644 --- a/webkit/browser/fileapi/local_file_system_operation_unittest.cc +++ b/webkit/browser/fileapi/local_file_system_operation_unittest.cc @@ -20,10 +20,10 @@ #include "webkit/browser/fileapi/file_system_operation_context.h" #include "webkit/browser/fileapi/mock_file_change_observer.h" #include "webkit/browser/fileapi/sandbox_file_system_test_helper.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/blob/shareable_file_reference.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/mock_quota_manager.h" -#include "webkit/quota/quota_manager.h" using quota::QuotaManager; using quota::QuotaManagerProxy; diff --git a/webkit/browser/fileapi/local_file_system_operation_write_unittest.cc b/webkit/browser/fileapi/local_file_system_operation_write_unittest.cc index bc42130..28849ac 100644 --- a/webkit/browser/fileapi/local_file_system_operation_write_unittest.cc +++ b/webkit/browser/fileapi/local_file_system_operation_write_unittest.cc @@ -26,9 +26,9 @@ #include "webkit/browser/fileapi/local_file_util.h" #include "webkit/browser/fileapi/mock_file_change_observer.h" #include "webkit/browser/fileapi/mock_file_system_context.h" +#include "webkit/browser/quota/mock_quota_manager.h" #include "webkit/common/blob/blob_data.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/mock_quota_manager.h" using webkit_blob::MockBlobURLRequestContext; using webkit_blob::ScopedTextBlob; diff --git a/webkit/browser/fileapi/mock_file_system_context.cc b/webkit/browser/fileapi/mock_file_system_context.cc index 5e222b8..4c1956e 100644 --- a/webkit/browser/fileapi/mock_file_system_context.cc +++ b/webkit/browser/fileapi/mock_file_system_context.cc @@ -11,7 +11,7 @@ #include "webkit/browser/fileapi/file_system_task_runners.h" #include "webkit/browser/fileapi/mock_file_system_options.h" #include "webkit/browser/fileapi/test_mount_point_provider.h" -#include "webkit/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" namespace fileapi { diff --git a/webkit/browser/fileapi/obfuscated_file_util.cc b/webkit/browser/fileapi/obfuscated_file_util.cc index 2971fe2..59dd7a8 100644 --- a/webkit/browser/fileapi/obfuscated_file_util.cc +++ b/webkit/browser/fileapi/obfuscated_file_util.cc @@ -26,8 +26,8 @@ #include "webkit/browser/fileapi/native_file_util.h" #include "webkit/browser/fileapi/sandbox_mount_point_provider.h" #include "webkit/browser/fileapi/syncable/syncable_file_system_util.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" // Example of various paths: // void ObfuscatedFileUtil::DoSomething(const FileSystemURL& url) { diff --git a/webkit/browser/fileapi/obfuscated_file_util_unittest.cc b/webkit/browser/fileapi/obfuscated_file_util_unittest.cc index a0e788d..0450772 100644 --- a/webkit/browser/fileapi/obfuscated_file_util_unittest.cc +++ b/webkit/browser/fileapi/obfuscated_file_util_unittest.cc @@ -26,9 +26,9 @@ #include "webkit/browser/fileapi/obfuscated_file_util.h" #include "webkit/browser/fileapi/sandbox_file_system_test_helper.h" #include "webkit/browser/fileapi/test_file_set.h" -#include "webkit/quota/mock_special_storage_policy.h" -#include "webkit/quota/quota_manager.h" -#include "webkit/quota/quota_types.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/common/quota/quota_types.h" namespace fileapi { diff --git a/webkit/browser/fileapi/sandbox_file_stream_writer.cc b/webkit/browser/fileapi/sandbox_file_stream_writer.cc index 161111f..cfdf25f 100644 --- a/webkit/browser/fileapi/sandbox_file_stream_writer.cc +++ b/webkit/browser/fileapi/sandbox_file_stream_writer.cc @@ -14,8 +14,8 @@ #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/file_system_operation.h" #include "webkit/browser/fileapi/local_file_stream_writer.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" namespace fileapi { diff --git a/webkit/browser/fileapi/sandbox_file_stream_writer.h b/webkit/browser/fileapi/sandbox_file_stream_writer.h index b6cce10..829f4c8 100644 --- a/webkit/browser/fileapi/sandbox_file_stream_writer.h +++ b/webkit/browser/fileapi/sandbox_file_stream_writer.h @@ -13,7 +13,7 @@ #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/task_runner_bound_observer_list.h" #include "webkit/common/fileapi/file_system_types.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" #include "webkit/storage/webkit_storage_export.h" namespace fileapi { diff --git a/webkit/browser/fileapi/sandbox_file_system_test_helper.cc b/webkit/browser/fileapi/sandbox_file_system_test_helper.cc index 0e9ba0b..5abcd45 100644 --- a/webkit/browser/fileapi/sandbox_file_system_test_helper.cc +++ b/webkit/browser/fileapi/sandbox_file_system_test_helper.cc @@ -17,8 +17,8 @@ #include "webkit/browser/fileapi/local_file_system_operation.h" #include "webkit/browser/fileapi/mock_file_system_context.h" #include "webkit/browser/fileapi/sandbox_mount_point_provider.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/mock_special_storage_policy.h" namespace fileapi { diff --git a/webkit/browser/fileapi/sandbox_file_system_test_helper.h b/webkit/browser/fileapi/sandbox_file_system_test_helper.h index c711713..5a8042b 100644 --- a/webkit/browser/fileapi/sandbox_file_system_test_helper.h +++ b/webkit/browser/fileapi/sandbox_file_system_test_helper.h @@ -14,7 +14,7 @@ #include "webkit/browser/fileapi/file_system_usage_cache.h" #include "webkit/common/fileapi/file_system_types.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" namespace base { class FilePath; diff --git a/webkit/browser/fileapi/sandbox_mount_point_provider.cc b/webkit/browser/fileapi/sandbox_mount_point_provider.cc index 0527b14..9d24b3f1 100644 --- a/webkit/browser/fileapi/sandbox_mount_point_provider.cc +++ b/webkit/browser/fileapi/sandbox_mount_point_provider.cc @@ -27,9 +27,9 @@ #include "webkit/browser/fileapi/sandbox_file_stream_writer.h" #include "webkit/browser/fileapi/sandbox_quota_observer.h" #include "webkit/browser/fileapi/syncable/syncable_file_system_operation.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_types.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" using quota::QuotaManagerProxy; diff --git a/webkit/browser/fileapi/sandbox_mount_point_provider.h b/webkit/browser/fileapi/sandbox_mount_point_provider.h index 3ae0ed1..d67afbb 100644 --- a/webkit/browser/fileapi/sandbox_mount_point_provider.h +++ b/webkit/browser/fileapi/sandbox_mount_point_provider.h @@ -19,7 +19,7 @@ #include "webkit/browser/fileapi/file_system_options.h" #include "webkit/browser/fileapi/file_system_quota_util.h" #include "webkit/browser/fileapi/task_runner_bound_observer_list.h" -#include "webkit/quota/special_storage_policy.h" +#include "webkit/browser/quota/special_storage_policy.h" #include "webkit/storage/webkit_storage_export.h" namespace base { diff --git a/webkit/browser/fileapi/sandbox_quota_observer.cc b/webkit/browser/fileapi/sandbox_quota_observer.cc index 0661a8e6..05fa511 100644 --- a/webkit/browser/fileapi/sandbox_quota_observer.cc +++ b/webkit/browser/fileapi/sandbox_quota_observer.cc @@ -8,9 +8,9 @@ #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/file_system_usage_cache.h" #include "webkit/browser/fileapi/sandbox_mount_point_provider.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_client.h" -#include "webkit/quota/quota_manager.h" namespace fileapi { diff --git a/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc b/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc index 53f3430..33b4e3e 100644 --- a/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc +++ b/webkit/browser/fileapi/syncable/canned_syncable_file_system.cc @@ -24,8 +24,8 @@ #include "webkit/browser/fileapi/syncable/local_file_change_tracker.h" #include "webkit/browser/fileapi/syncable/local_file_sync_context.h" #include "webkit/browser/fileapi/syncable/syncable_file_system_util.h" -#include "webkit/quota/mock_special_storage_policy.h" -#include "webkit/quota/quota_manager.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/quota_manager.h" using base::PlatformFileError; using fileapi::FileSystemContext; diff --git a/webkit/browser/fileapi/syncable/canned_syncable_file_system.h b/webkit/browser/fileapi/syncable/canned_syncable_file_system.h index 9fb4831..7d0b52e 100644 --- a/webkit/browser/fileapi/syncable/canned_syncable_file_system.h +++ b/webkit/browser/fileapi/syncable/canned_syncable_file_system.h @@ -16,10 +16,10 @@ #include "webkit/browser/fileapi/file_system_url.h" #include "webkit/browser/fileapi/syncable/local_file_sync_status.h" #include "webkit/browser/fileapi/syncable/sync_status_code.h" +#include "webkit/browser/quota/quota_callbacks.h" #include "webkit/common/fileapi/file_system_types.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_callbacks.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" namespace base { class MessageLoopProxy; diff --git a/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc b/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc index 78fb5fe..834389d 100644 --- a/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc +++ b/webkit/browser/fileapi/syncable/local_file_change_tracker_unittest.cc @@ -21,7 +21,7 @@ #include "webkit/browser/fileapi/syncable/local_file_sync_context.h" #include "webkit/browser/fileapi/syncable/sync_status_code.h" #include "webkit/browser/fileapi/syncable/syncable_file_system_util.h" -#include "webkit/quota/quota_manager.h" +#include "webkit/browser/quota/quota_manager.h" using fileapi::FileSystemContext; using fileapi::FileSystemURL; diff --git a/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc b/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc index a1d25e1..36e5b6a 100644 --- a/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc +++ b/webkit/browser/fileapi/syncable/syncable_file_system_unittest.cc @@ -15,9 +15,9 @@ #include "webkit/browser/fileapi/syncable/local_file_change_tracker.h" #include "webkit/browser/fileapi/syncable/local_file_sync_context.h" #include "webkit/browser/fileapi/syncable/syncable_file_system_util.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_types.h" -#include "webkit/quota/quota_manager.h" -#include "webkit/quota/quota_types.h" +#include "webkit/common/quota/quota_types.h" using base::PlatformFileError; using fileapi::FileSystemContext; diff --git a/webkit/browser/fileapi/test_mount_point_provider.cc b/webkit/browser/fileapi/test_mount_point_provider.cc index c005d0b..0c87035 100644 --- a/webkit/browser/fileapi/test_mount_point_provider.cc +++ b/webkit/browser/fileapi/test_mount_point_provider.cc @@ -19,8 +19,8 @@ #include "webkit/browser/fileapi/local_file_util.h" #include "webkit/browser/fileapi/native_file_util.h" #include "webkit/browser/fileapi/sandbox_file_stream_writer.h" +#include "webkit/browser/quota/quota_manager.h" #include "webkit/common/fileapi/file_system_util.h" -#include "webkit/quota/quota_manager.h" namespace fileapi { diff --git a/webkit/browser/quota/mock_quota_manager.cc b/webkit/browser/quota/mock_quota_manager.cc new file mode 100644 index 0000000..30a0ebf --- /dev/null +++ b/webkit/browser/quota/mock_quota_manager.cc @@ -0,0 +1,199 @@ +// Copyright 2013 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 "webkit/browser/quota/mock_quota_manager.h" + +#include <set> +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "googleurl/src/gurl.h" + +namespace quota { + +MockQuotaManager::OriginInfo::OriginInfo( + const GURL& origin, + StorageType type, + int quota_client_mask, + base::Time modified) + : origin(origin), + type(type), + quota_client_mask(quota_client_mask), + modified(modified) { +} + +MockQuotaManager::OriginInfo::~OriginInfo() {} + +MockQuotaManager::StorageInfo::StorageInfo() : usage(0), quota(kint64max) {} +MockQuotaManager::StorageInfo::~StorageInfo() {} + +// MockQuotaManager ---------------------------------------------------------- + +MockQuotaManager::MockQuotaManager( + bool is_incognito, + const base::FilePath& profile_path, + base::SingleThreadTaskRunner* io_thread, + base::SequencedTaskRunner* db_thread, + SpecialStoragePolicy* special_storage_policy) + : QuotaManager(is_incognito, profile_path, io_thread, db_thread, + special_storage_policy), + weak_factory_(this) { +} + +void MockQuotaManager::GetUsageAndQuota( + const GURL& origin, + quota::StorageType type, + const GetUsageAndQuotaCallback& callback) { + StorageInfo& info = usage_and_quota_map_[std::make_pair(origin, type)]; + callback.Run(quota::kQuotaStatusOk, info.usage, info.quota); +} + +void MockQuotaManager::SetQuota(const GURL& origin, StorageType type, + int64 quota) { + usage_and_quota_map_[std::make_pair(origin, type)].quota = quota; +} + +bool MockQuotaManager::AddOrigin( + const GURL& origin, + StorageType type, + int quota_client_mask, + base::Time modified) { + origins_.push_back(OriginInfo(origin, type, quota_client_mask, modified)); + return true; +} + +bool MockQuotaManager::OriginHasData( + const GURL& origin, + StorageType type, + QuotaClient::ID quota_client) const { + for (std::vector<OriginInfo>::const_iterator current = origins_.begin(); + current != origins_.end(); + ++current) { + if (current->origin == origin && + current->type == type && + current->quota_client_mask & quota_client) + return true; + } + return false; +} + +void MockQuotaManager::GetOriginsModifiedSince( + StorageType type, + base::Time modified_since, + const GetOriginsCallback& callback) { + std::set<GURL>* origins_to_return = new std::set<GURL>(); + for (std::vector<OriginInfo>::const_iterator current = origins_.begin(); + current != origins_.end(); + ++current) { + if (current->type == type && current->modified >= modified_since) + origins_to_return->insert(current->origin); + } + + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MockQuotaManager::DidGetModifiedSince, + weak_factory_.GetWeakPtr(), + callback, + base::Owned(origins_to_return), + type)); +} + +void MockQuotaManager::DeleteOriginData( + const GURL& origin, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) { + for (std::vector<OriginInfo>::iterator current = origins_.begin(); + current != origins_.end(); + ++current) { + if (current->origin == origin && current->type == type) { + // Modify the mask: if it's 0 after "deletion", remove the origin. + current->quota_client_mask &= ~quota_client_mask; + if (current->quota_client_mask == 0) + origins_.erase(current); + break; + } + } + + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&MockQuotaManager::DidDeleteOriginData, + weak_factory_.GetWeakPtr(), + callback, + kQuotaStatusOk)); +} + +MockQuotaManager::~MockQuotaManager() {} + +void MockQuotaManager::UpdateUsage( + const GURL& origin, StorageType type, int64 delta) { + usage_and_quota_map_[std::make_pair(origin, type)].usage += delta; +} + +void MockQuotaManager::DidGetModifiedSince( + const GetOriginsCallback& callback, + std::set<GURL>* origins, + StorageType storage_type) { + callback.Run(*origins, storage_type); +} + +void MockQuotaManager::DidDeleteOriginData( + const StatusCallback& callback, + QuotaStatusCode status) { + callback.Run(status); +} + +// MockQuotaManagerProxy ----------------------------------------------------- + +MockQuotaManagerProxy::MockQuotaManagerProxy( + MockQuotaManager* quota_manager, + base::SingleThreadTaskRunner* task_runner) + : QuotaManagerProxy(quota_manager, task_runner), + storage_accessed_count_(0), + storage_modified_count_(0), + last_notified_type_(kStorageTypeUnknown), + last_notified_delta_(0), + registered_client_(NULL) {} + +void MockQuotaManagerProxy::RegisterClient(QuotaClient* client) { + DCHECK(!registered_client_); + registered_client_ = client; +} + +void MockQuotaManagerProxy::SimulateQuotaManagerDestroyed() { + if (registered_client_) { + // We cannot call this in the destructor as the client (indirectly) + // holds a refptr of the proxy. + registered_client_->OnQuotaManagerDestroyed(); + registered_client_ = NULL; + } +} + +void MockQuotaManagerProxy::NotifyStorageAccessed( + QuotaClient::ID client_id, const GURL& origin, StorageType type) { + ++storage_accessed_count_; + last_notified_origin_ = origin; + last_notified_type_ = type; +} + +void MockQuotaManagerProxy::NotifyStorageModified( + QuotaClient::ID client_id, const GURL& origin, + StorageType type, int64 delta) { + ++storage_modified_count_; + last_notified_origin_ = origin; + last_notified_type_ = type; + last_notified_delta_ = delta; + if (mock_manager()) + mock_manager()->UpdateUsage(origin, type, delta); +} + +MockQuotaManagerProxy::~MockQuotaManagerProxy() { + DCHECK(!registered_client_); +} + +} // namespace quota diff --git a/webkit/browser/quota/mock_quota_manager.h b/webkit/browser/quota/mock_quota_manager.h new file mode 100644 index 0000000..3baa3e6 --- /dev/null +++ b/webkit/browser/quota/mock_quota_manager.h @@ -0,0 +1,197 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_MOCK_QUOTA_MANAGER_H_ +#define WEBKIT_BROWSER_QUOTA_MOCK_QUOTA_MANAGER_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "googleurl/src/gurl.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/quota_task.h" +#include "webkit/common/quota/quota_types.h" + +namespace quota { + +// Mocks the pieces of QuotaManager's interface. +// +// For usage/quota tracking test: +// Usage and quota information can be updated by following private helper +// methods: SetQuota() and UpdateUsage(). +// +// For time-based deletion test: +// Origins can be added to the mock by calling AddOrigin, and that list of +// origins is then searched through in GetOriginsModifiedSince. +// Neither GetOriginsModifiedSince nor DeleteOriginData touches the actual +// origin data stored in the profile. +class MockQuotaManager : public QuotaManager { + public: + MockQuotaManager(bool is_incognito, + const base::FilePath& profile_path, + base::SingleThreadTaskRunner* io_thread, + base::SequencedTaskRunner* db_thread, + SpecialStoragePolicy* special_storage_policy); + + // Overrides QuotaManager's implementation. The internal usage data is + // updated when MockQuotaManagerProxy::NotifyStorageModified() is + // called. The internal quota value can be updated by calling + // a helper method MockQuotaManagerProxy::SetQuota(). + virtual void GetUsageAndQuota( + const GURL& origin, + quota::StorageType type, + const GetUsageAndQuotaCallback& callback) OVERRIDE; + + // Overrides QuotaManager's implementation with a canned implementation that + // allows clients to set up the origin database that should be queried. This + // method will only search through the origins added explicitly via AddOrigin. + virtual void GetOriginsModifiedSince( + StorageType type, + base::Time modified_since, + const GetOriginsCallback& callback) OVERRIDE; + + // Removes an origin from the canned list of origins, but doesn't touch + // anything on disk. The caller must provide |quota_client_mask| which + // specifies the types of QuotaClients which should be removed from this + // origin as a bitmask built from QuotaClient::IDs. Setting the mask to + // QuotaClient::kAllClientsMask will remove all clients from the origin, + // regardless of type. + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) OVERRIDE; + + // Helper method for updating internal quota info. + void SetQuota(const GURL& origin, StorageType type, int64 quota); + + // Helper methods for timed-deletion testing: + // Adds an origin to the canned list that will be searched through via + // GetOriginsModifiedSince. The caller must provide |quota_client_mask| + // which specifies the types of QuotaClients this canned origin contains + // as a bitmask built from QuotaClient::IDs. + bool AddOrigin(const GURL& origin, + StorageType type, + int quota_client_mask, + base::Time modified); + + // Helper methods for timed-deletion testing: + // Checks an origin and type against the origins that have been added via + // AddOrigin and removed via DeleteOriginData. If the origin exists in the + // canned list with the proper StorageType and client, returns true. + bool OriginHasData(const GURL& origin, + StorageType type, + QuotaClient::ID quota_client) const; + + protected: + virtual ~MockQuotaManager(); + + private: + friend class MockQuotaManagerProxy; + + // Contains the essential bits of information about an origin that the + // MockQuotaManager needs to understand for time-based deletion: + // the origin itself, the StorageType and its modification time. + struct OriginInfo { + OriginInfo(const GURL& origin, + StorageType type, + int quota_client_mask, + base::Time modified); + ~OriginInfo(); + + GURL origin; + StorageType type; + int quota_client_mask; + base::Time modified; + }; + + // Contains the essential information for each origin for usage/quota testing. + // (Ideally this should probably merged into the above struct, but for + // regular usage/quota testing we hardly need modified time but only + // want to keep usage and quota information, so this struct exists. + struct StorageInfo { + StorageInfo(); + ~StorageInfo(); + int64 usage; + int64 quota; + }; + + typedef std::pair<GURL, StorageType> OriginAndType; + typedef std::map<OriginAndType, StorageInfo> UsageAndQuotaMap; + + // This must be called via MockQuotaManagerProxy. + void UpdateUsage(const GURL& origin, StorageType type, int64 delta); + void DidGetModifiedSince(const GetOriginsCallback& callback, + std::set<GURL>* origins, + StorageType storage_type); + void DidDeleteOriginData(const StatusCallback& callback, + QuotaStatusCode status); + + // The list of stored origins that have been added via AddOrigin. + std::vector<OriginInfo> origins_; + base::WeakPtrFactory<MockQuotaManager> weak_factory_; + UsageAndQuotaMap usage_and_quota_map_; + + DISALLOW_COPY_AND_ASSIGN(MockQuotaManager); +}; + +// MockQuotaManagerProxy. +class MockQuotaManagerProxy : public QuotaManagerProxy { + public: + // It is ok to give NULL to |quota_manager|. + MockQuotaManagerProxy(MockQuotaManager* quota_manager, + base::SingleThreadTaskRunner* task_runner); + + virtual void RegisterClient(QuotaClient* client) OVERRIDE; + + void SimulateQuotaManagerDestroyed(); + + // We don't mock them. + virtual void NotifyOriginInUse(const GURL& origin) OVERRIDE {} + virtual void NotifyOriginNoLongerInUse(const GURL& origin) OVERRIDE {} + + // Validates the |client_id| and updates the internal access count + // which can be accessed via notify_storage_accessed_count(). + // The also records the |origin| and |type| in last_notified_origin_ and + // last_notified_type_. + virtual void NotifyStorageAccessed(QuotaClient::ID client_id, + const GURL& origin, + StorageType type) OVERRIDE; + + // Records the |origin|, |type| and |delta| as last_notified_origin_, + // last_notified_type_ and last_notified_delta_ respecitvely. + // If non-null MockQuotaManager is given to the constructor this also + // updates the manager's internal usage information. + virtual void NotifyStorageModified(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta) OVERRIDE; + + int notify_storage_accessed_count() const { return storage_accessed_count_; } + int notify_storage_modified_count() const { return storage_modified_count_; } + GURL last_notified_origin() const { return last_notified_origin_; } + StorageType last_notified_type() const { return last_notified_type_; } + int64 last_notified_delta() const { return last_notified_delta_; } + + protected: + virtual ~MockQuotaManagerProxy(); + + private: + MockQuotaManager* mock_manager() const { + return static_cast<MockQuotaManager*>(quota_manager()); + } + + int storage_accessed_count_; + int storage_modified_count_; + GURL last_notified_origin_; + StorageType last_notified_type_; + int64 last_notified_delta_; + + QuotaClient* registered_client_; +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_MOCK_QUOTA_MANAGER_H_ diff --git a/webkit/browser/quota/mock_quota_manager_unittest.cc b/webkit/browser/quota/mock_quota_manager_unittest.cc new file mode 100644 index 0000000..923a1fe --- /dev/null +++ b/webkit/browser/quota/mock_quota_manager_unittest.cc @@ -0,0 +1,223 @@ +// Copyright 2013 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 <set> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/quota/mock_quota_manager.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/mock_storage_client.h" + +namespace quota { + +const char kTestOrigin1[] = "http://host1:1/"; +const char kTestOrigin2[] = "http://host2:1/"; +const char kTestOrigin3[] = "http://host3:1/"; + +const GURL kOrigin1(kTestOrigin1); +const GURL kOrigin2(kTestOrigin2); +const GURL kOrigin3(kTestOrigin3); + +const StorageType kTemporary = kStorageTypeTemporary; +const StorageType kPersistent = kStorageTypePersistent; + +const QuotaClient::ID kClientFile = QuotaClient::kFileSystem; +const QuotaClient::ID kClientDB = QuotaClient::kIndexedDatabase; + +class MockQuotaManagerTest : public testing::Test { + public: + MockQuotaManagerTest() + : weak_factory_(this), + deletion_callback_count_(0) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + policy_ = new MockSpecialStoragePolicy; + manager_ = new MockQuotaManager( + false /* is_incognito */, + data_dir_.path(), + base::MessageLoopProxy::current(), + base::MessageLoopProxy::current(), + policy_); + } + + virtual void TearDown() { + // Make sure the quota manager cleans up correctly. + manager_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + void GetModifiedOrigins(StorageType type, base::Time since) { + manager_->GetOriginsModifiedSince( + type, since, + base::Bind(&MockQuotaManagerTest::GotModifiedOrigins, + weak_factory_.GetWeakPtr())); + } + + void GotModifiedOrigins(const std::set<GURL>& origins, StorageType type) { + origins_ = origins; + type_ = type; + } + + void DeleteOriginData(const GURL& origin, StorageType type, + int quota_client_mask) { + manager_->DeleteOriginData( + origin, type, quota_client_mask, + base::Bind(&MockQuotaManagerTest::DeletedOriginData, + weak_factory_.GetWeakPtr())); + } + + void DeletedOriginData(QuotaStatusCode status) { + ++deletion_callback_count_; + EXPECT_EQ(quota::kQuotaStatusOk, status); + } + + int deletion_callback_count() const { + return deletion_callback_count_; + } + + MockQuotaManager* manager() const { + return manager_.get(); + } + + const std::set<GURL>& origins() const { + return origins_; + } + + const StorageType& type() const { + return type_; + } + + private: + base::MessageLoop message_loop_; + base::ScopedTempDir data_dir_; + base::WeakPtrFactory<MockQuotaManagerTest> weak_factory_; + scoped_refptr<MockQuotaManager> manager_; + scoped_refptr<MockSpecialStoragePolicy> policy_; + + int deletion_callback_count_; + + std::set<GURL> origins_; + StorageType type_; + + DISALLOW_COPY_AND_ASSIGN(MockQuotaManagerTest); +}; + +TEST_F(MockQuotaManagerTest, BasicOriginManipulation) { + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kPersistent, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientDB)); + + manager()->AddOrigin(kOrigin1, kTemporary, kClientFile, base::Time::Now()); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kPersistent, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientDB)); + + manager()->AddOrigin(kOrigin1, kPersistent, kClientFile, base::Time::Now()); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kTemporary, kClientDB)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kPersistent, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientDB)); + + manager()->AddOrigin(kOrigin2, kTemporary, kClientFile | kClientDB, + base::Time::Now()); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kTemporary, kClientDB)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin1, kPersistent, kClientDB)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin2, kTemporary, kClientFile)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin2, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kPersistent, kClientDB)); +} + +TEST_F(MockQuotaManagerTest, OriginDeletion) { + manager()->AddOrigin(kOrigin1, kTemporary, kClientFile, base::Time::Now()); + manager()->AddOrigin(kOrigin2, kTemporary, kClientFile | kClientDB, + base::Time::Now()); + manager()->AddOrigin(kOrigin3, kTemporary, kClientFile | kClientDB, + base::Time::Now()); + + DeleteOriginData(kOrigin2, kTemporary, kClientFile); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(1, deletion_callback_count()); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientFile)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin2, kTemporary, kClientDB)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin3, kTemporary, kClientFile)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin3, kTemporary, kClientDB)); + + DeleteOriginData(kOrigin3, kTemporary, kClientFile | kClientDB); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(2, deletion_callback_count()); + EXPECT_TRUE(manager()->OriginHasData(kOrigin1, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin2, kTemporary, kClientFile)); + EXPECT_TRUE(manager()->OriginHasData(kOrigin2, kTemporary, kClientDB)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin3, kTemporary, kClientFile)); + EXPECT_FALSE(manager()->OriginHasData(kOrigin3, kTemporary, kClientDB)); +} + +TEST_F(MockQuotaManagerTest, ModifiedOrigins) { + base::Time now = base::Time::Now(); + base::Time then = base::Time(); + base::TimeDelta an_hour = base::TimeDelta::FromMilliseconds(3600000); + base::TimeDelta a_minute = base::TimeDelta::FromMilliseconds(60000); + + GetModifiedOrigins(kTemporary, then); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(origins().empty()); + + manager()->AddOrigin(kOrigin1, kTemporary, kClientFile, now - an_hour); + + GetModifiedOrigins(kTemporary, then); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(kTemporary, type()); + EXPECT_EQ(1UL, origins().size()); + EXPECT_EQ(1UL, origins().count(kOrigin1)); + EXPECT_EQ(0UL, origins().count(kOrigin2)); + + manager()->AddOrigin(kOrigin2, kTemporary, kClientFile, now); + + GetModifiedOrigins(kTemporary, then); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(kTemporary, type()); + EXPECT_EQ(2UL, origins().size()); + EXPECT_EQ(1UL, origins().count(kOrigin1)); + EXPECT_EQ(1UL, origins().count(kOrigin2)); + + GetModifiedOrigins(kTemporary, now - a_minute); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(kTemporary, type()); + EXPECT_EQ(1UL, origins().size()); + EXPECT_EQ(0UL, origins().count(kOrigin1)); + EXPECT_EQ(1UL, origins().count(kOrigin2)); +} +} // Namespace quota diff --git a/webkit/browser/quota/mock_special_storage_policy.cc b/webkit/browser/quota/mock_special_storage_policy.cc new file mode 100644 index 0000000..3cb0e66 --- /dev/null +++ b/webkit/browser/quota/mock_special_storage_policy.cc @@ -0,0 +1,41 @@ +// Copyright 2013 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 "webkit/browser/quota/mock_special_storage_policy.h" + +namespace quota { + +MockSpecialStoragePolicy::MockSpecialStoragePolicy() + : all_unlimited_(false) { +} + +bool MockSpecialStoragePolicy::IsStorageProtected(const GURL& origin) { + return protected_.find(origin) != protected_.end(); +} + +bool MockSpecialStoragePolicy::IsStorageUnlimited(const GURL& origin) { + if (all_unlimited_) + return true; + return unlimited_.find(origin) != unlimited_.end(); +} + +bool MockSpecialStoragePolicy::IsStorageSessionOnly(const GURL& origin) { + return session_only_.find(origin) != session_only_.end(); +} + +bool MockSpecialStoragePolicy::CanQueryDiskSize(const GURL& origin) { + return can_query_disk_size_.find(origin) != can_query_disk_size_.end(); +} + +bool MockSpecialStoragePolicy::IsFileHandler(const std::string& extension_id) { + return file_handlers_.find(extension_id) != file_handlers_.end(); +} + +bool MockSpecialStoragePolicy::HasSessionOnlyOrigins() { + return !session_only_.empty(); +} + +MockSpecialStoragePolicy::~MockSpecialStoragePolicy() {} + +} // namespace quota diff --git a/webkit/browser/quota/mock_special_storage_policy.h b/webkit/browser/quota/mock_special_storage_policy.h new file mode 100644 index 0000000..1cfaa1e --- /dev/null +++ b/webkit/browser/quota/mock_special_storage_policy.h @@ -0,0 +1,90 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_MOCK_SPECIAL_STORAGE_POLICY_H_ +#define WEBKIT_BROWSER_QUOTA_MOCK_SPECIAL_STORAGE_POLICY_H_ + +#include <set> +#include <string> + +#include "googleurl/src/gurl.h" +#include "webkit/browser/quota/special_storage_policy.h" + +namespace quota { + +class MockSpecialStoragePolicy : public quota::SpecialStoragePolicy { + public: + MockSpecialStoragePolicy(); + + virtual bool IsStorageProtected(const GURL& origin) OVERRIDE; + virtual bool IsStorageUnlimited(const GURL& origin) OVERRIDE; + virtual bool IsStorageSessionOnly(const GURL& origin) OVERRIDE; + virtual bool CanQueryDiskSize(const GURL& origin) OVERRIDE; + virtual bool IsFileHandler(const std::string& extension_id) OVERRIDE; + virtual bool HasSessionOnlyOrigins() OVERRIDE; + + void AddProtected(const GURL& origin) { + protected_.insert(origin); + } + + void AddUnlimited(const GURL& origin) { + unlimited_.insert(origin); + } + + void RemoveUnlimited(const GURL& origin) { + unlimited_.erase(origin); + } + + void AddSessionOnly(const GURL& origin) { + session_only_.insert(origin); + } + + void GrantQueryDiskSize(const GURL& origin) { + can_query_disk_size_.insert(origin); + } + + void AddFileHandler(const std::string& id) { + file_handlers_.insert(id); + } + + void SetAllUnlimited(bool all_unlimited) { + all_unlimited_ = all_unlimited; + } + + void Reset() { + protected_.clear(); + unlimited_.clear(); + session_only_.clear(); + can_query_disk_size_.clear(); + file_handlers_.clear(); + all_unlimited_ = false; + } + + void NotifyGranted(const GURL& origin, int change_flags) { + SpecialStoragePolicy::NotifyGranted(origin, change_flags); + } + + void NotifyRevoked(const GURL& origin, int change_flags) { + SpecialStoragePolicy::NotifyRevoked(origin, change_flags); + } + + void NotifyCleared() { + SpecialStoragePolicy::NotifyCleared(); + } + + protected: + virtual ~MockSpecialStoragePolicy(); + + private: + std::set<GURL> protected_; + std::set<GURL> unlimited_; + std::set<GURL> session_only_; + std::set<GURL> can_query_disk_size_; + std::set<std::string> file_handlers_; + + bool all_unlimited_; +}; +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_MOCK_SPECIAL_STORAGE_POLICY_H_ diff --git a/webkit/browser/quota/mock_storage_client.cc b/webkit/browser/quota/mock_storage_client.cc new file mode 100644 index 0000000..c0d03c4 --- /dev/null +++ b/webkit/browser/quota/mock_storage_client.cc @@ -0,0 +1,182 @@ +// Copyright 2013 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 "webkit/browser/quota/mock_storage_client.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/message_loop_proxy.h" +#include "base/stl_util.h" +#include "net/base/net_util.h" +#include "webkit/browser/quota/quota_manager.h" + +namespace quota { + +using std::make_pair; + +MockStorageClient::MockStorageClient( + QuotaManagerProxy* quota_manager_proxy, + const MockOriginData* mock_data, QuotaClient::ID id, size_t mock_data_size) + : quota_manager_proxy_(quota_manager_proxy), + id_(id), + mock_time_counter_(0), + weak_factory_(this) { + Populate(mock_data, mock_data_size); +} + +void MockStorageClient::Populate( + const MockOriginData* mock_data, + size_t mock_data_size) { + for (size_t i = 0; i < mock_data_size; ++i) { + origin_data_[make_pair(GURL(mock_data[i].origin), mock_data[i].type)] = + mock_data[i].usage; + } +} + +MockStorageClient::~MockStorageClient() {} + +void MockStorageClient::AddOriginAndNotify( + const GURL& origin_url, StorageType type, int64 size) { + DCHECK(origin_data_.find(make_pair(origin_url, type)) == origin_data_.end()); + DCHECK_GE(size, 0); + origin_data_[make_pair(origin_url, type)] = size; + quota_manager_proxy_->quota_manager()->NotifyStorageModifiedInternal( + id(), origin_url, type, size, IncrementMockTime()); +} + +void MockStorageClient::ModifyOriginAndNotify( + const GURL& origin_url, StorageType type, int64 delta) { + OriginDataMap::iterator find = origin_data_.find(make_pair(origin_url, type)); + DCHECK(find != origin_data_.end()); + find->second += delta; + DCHECK_GE(find->second, 0); + + // TODO(tzik): Check quota to prevent usage exceed + quota_manager_proxy_->quota_manager()->NotifyStorageModifiedInternal( + id(), origin_url, type, delta, IncrementMockTime()); +} + +void MockStorageClient::TouchAllOriginsAndNotify() { + for (OriginDataMap::const_iterator itr = origin_data_.begin(); + itr != origin_data_.end(); + ++itr) { + quota_manager_proxy_->quota_manager()->NotifyStorageModifiedInternal( + id(), itr->first.first, itr->first.second, 0, IncrementMockTime()); + } +} + +void MockStorageClient::AddOriginToErrorSet( + const GURL& origin_url, StorageType type) { + error_origins_.insert(make_pair(origin_url, type)); +} + +base::Time MockStorageClient::IncrementMockTime() { + ++mock_time_counter_; + return base::Time::FromDoubleT(mock_time_counter_ * 10.0); +} + +QuotaClient::ID MockStorageClient::id() const { + return id_; +} + +void MockStorageClient::OnQuotaManagerDestroyed() { + delete this; +} + +void MockStorageClient::GetOriginUsage(const GURL& origin_url, + StorageType type, + const GetUsageCallback& callback) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockStorageClient::RunGetOriginUsage, + weak_factory_.GetWeakPtr(), origin_url, type, callback)); +} + +void MockStorageClient::GetOriginsForType( + StorageType type, const GetOriginsCallback& callback) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockStorageClient::RunGetOriginsForType, + weak_factory_.GetWeakPtr(), type, callback)); +} + +void MockStorageClient::GetOriginsForHost( + StorageType type, const std::string& host, + const GetOriginsCallback& callback) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockStorageClient::RunGetOriginsForHost, + weak_factory_.GetWeakPtr(), type, host, callback)); +} + +void MockStorageClient::DeleteOriginData( + const GURL& origin, StorageType type, + const DeletionCallback& callback) { + base::MessageLoopProxy::current()->PostTask( + FROM_HERE, + base::Bind(&MockStorageClient::RunDeleteOriginData, + weak_factory_.GetWeakPtr(), origin, type, callback)); +} + +void MockStorageClient::RunGetOriginUsage( + const GURL& origin_url, StorageType type, + const GetUsageCallback& callback) { + OriginDataMap::iterator find = origin_data_.find(make_pair(origin_url, type)); + if (find == origin_data_.end()) { + callback.Run(0); + } else { + callback.Run(find->second); + } +} + +void MockStorageClient::RunGetOriginsForType( + StorageType type, const GetOriginsCallback& callback) { + std::set<GURL> origins; + for (OriginDataMap::iterator iter = origin_data_.begin(); + iter != origin_data_.end(); ++iter) { + if (type == iter->first.second) + origins.insert(iter->first.first); + } + callback.Run(origins); +} + +void MockStorageClient::RunGetOriginsForHost( + StorageType type, const std::string& host, + const GetOriginsCallback& callback) { + std::set<GURL> origins; + for (OriginDataMap::iterator iter = origin_data_.begin(); + iter != origin_data_.end(); ++iter) { + std::string host_or_spec = net::GetHostOrSpecFromURL(iter->first.first); + if (type == iter->first.second && host == host_or_spec) + origins.insert(iter->first.first); + } + callback.Run(origins); +} + +void MockStorageClient::RunDeleteOriginData( + const GURL& origin_url, + StorageType type, + const DeletionCallback& callback) { + ErrorOriginSet::iterator itr_error = + error_origins_.find(make_pair(origin_url, type)); + if (itr_error != error_origins_.end()) { + callback.Run(kQuotaErrorInvalidModification); + return; + } + + OriginDataMap::iterator itr = + origin_data_.find(make_pair(origin_url, type)); + if (itr != origin_data_.end()) { + int64 delta = itr->second; + quota_manager_proxy_-> + NotifyStorageModified(id(), origin_url, type, -delta); + origin_data_.erase(itr); + } + + callback.Run(kQuotaStatusOk); +} + +} // namespace quota diff --git a/webkit/browser/quota/mock_storage_client.h b/webkit/browser/quota/mock_storage_client.h new file mode 100644 index 0000000..56ab53d --- /dev/null +++ b/webkit/browser/quota/mock_storage_client.h @@ -0,0 +1,95 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_MOCK_STORAGE_CLIENT_H_ +#define WEBKIT_BROWSER_QUOTA_MOCK_STORAGE_CLIENT_H_ + +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "webkit/browser/quota/quota_client.h" + +namespace quota { + +class QuotaManagerProxy; + +struct MockOriginData { + const char* origin; + StorageType type; + int64 usage; +}; + +// Mock storage class for testing. +class MockStorageClient : public QuotaClient { + public: + MockStorageClient(QuotaManagerProxy* quota_manager_proxy, + const MockOriginData* mock_data, + QuotaClient::ID id, + size_t mock_data_size); + virtual ~MockStorageClient(); + + // To add or modify mock data in this client. + void AddOriginAndNotify( + const GURL& origin_url, StorageType type, int64 size); + void ModifyOriginAndNotify( + const GURL& origin_url, StorageType type, int64 delta); + void TouchAllOriginsAndNotify(); + + void AddOriginToErrorSet(const GURL& origin_url, StorageType type); + + base::Time IncrementMockTime(); + + // QuotaClient methods. + virtual QuotaClient::ID id() const OVERRIDE; + virtual void OnQuotaManagerDestroyed() OVERRIDE; + virtual void GetOriginUsage(const GURL& origin_url, + StorageType type, + const GetUsageCallback& callback) OVERRIDE; + virtual void GetOriginsForType(StorageType type, + const GetOriginsCallback& callback) OVERRIDE; + virtual void GetOriginsForHost(StorageType type, const std::string& host, + const GetOriginsCallback& callback) OVERRIDE; + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + const DeletionCallback& callback) OVERRIDE; + + private: + void RunGetOriginUsage(const GURL& origin_url, + StorageType type, + const GetUsageCallback& callback); + void RunGetOriginsForType(StorageType type, + const GetOriginsCallback& callback); + void RunGetOriginsForHost(StorageType type, + const std::string& host, + const GetOriginsCallback& callback); + void RunDeleteOriginData(const GURL& origin_url, + StorageType type, + const DeletionCallback& callback); + + void Populate(const MockOriginData* mock_data, size_t mock_data_size); + + scoped_refptr<QuotaManagerProxy> quota_manager_proxy_; + const ID id_; + + typedef std::map<std::pair<GURL, StorageType>, int64> OriginDataMap; + OriginDataMap origin_data_; + typedef std::set<std::pair<GURL, StorageType> > ErrorOriginSet; + ErrorOriginSet error_origins_; + + int mock_time_counter_; + + base::WeakPtrFactory<MockStorageClient> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(MockStorageClient); +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_MOCK_STORAGE_CLIENT_H_ diff --git a/webkit/browser/quota/quota_callbacks.h b/webkit/browser/quota/quota_callbacks.h new file mode 100644 index 0000000..cc0e735 --- /dev/null +++ b/webkit/browser/quota/quota_callbacks.h @@ -0,0 +1,127 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_QUOTA_CALLBACKS_H_ +#define WEBKIT_BROWSER_QUOTA_QUOTA_CALLBACKS_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/tuple.h" +#include "webkit/common/quota/quota_status_code.h" +#include "webkit/common/quota/quota_types.h" + +class GURL; + +namespace quota { + +struct UsageInfo; +typedef std::vector<UsageInfo> UsageInfoEntries; + +// Common callback types that are used throughout in the quota module. +typedef base::Callback<void(int64 usage, + int64 unlimited_usage)> GlobalUsageCallback; +typedef base::Callback<void(QuotaStatusCode status, int64 quota)> QuotaCallback; +typedef base::Callback<void(int64 usage)> UsageCallback; +typedef base::Callback<void(QuotaStatusCode, int64)> AvailableSpaceCallback; +typedef base::Callback<void(QuotaStatusCode)> StatusCallback; +typedef base::Callback<void(const std::set<GURL>& origins, + StorageType type)> GetOriginsCallback; +typedef base::Callback<void(const UsageInfoEntries&)> GetUsageInfoCallback; + +template<typename CallbackType, typename Args> +void DispatchToCallback(const CallbackType& callback, + const Args& args) { + DispatchToMethod(&callback, &CallbackType::Run, args); +} + +// Simple template wrapper for a callback queue. +template <typename CallbackType, typename Args> +class CallbackQueue { + public: + // Returns true if the given |callback| is the first one added to the queue. + bool Add(const CallbackType& callback) { + callbacks_.push_back(callback); + return (callbacks_.size() == 1); + } + + bool HasCallbacks() const { + return !callbacks_.empty(); + } + + // Runs the callbacks added to the queue and clears the queue. + void Run(const Args& args) { + typedef typename std::vector<CallbackType>::iterator iterator; + for (iterator iter = callbacks_.begin(); + iter != callbacks_.end(); ++iter) + DispatchToCallback(*iter, args); + callbacks_.clear(); + } + + private: + std::vector<CallbackType> callbacks_; +}; + +typedef CallbackQueue<GlobalUsageCallback, + Tuple2<int64, int64> > + GlobalUsageCallbackQueue; +typedef CallbackQueue<AvailableSpaceCallback, + Tuple2<QuotaStatusCode, int64> > + AvailableSpaceCallbackQueue; +typedef CallbackQueue<QuotaCallback, + Tuple2<QuotaStatusCode, int64> > + GlobalQuotaCallbackQueue; +typedef CallbackQueue<base::Closure, Tuple0> ClosureQueue; + +template <typename CallbackType, typename Key, typename Args> +class CallbackQueueMap { + public: + typedef CallbackQueue<CallbackType, Args> CallbackQueueType; + typedef std::map<Key, CallbackQueueType> CallbackMap; + typedef typename CallbackMap::iterator iterator; + + bool Add(const Key& key, const CallbackType& callback) { + return callback_map_[key].Add(callback); + } + + bool HasCallbacks(const Key& key) const { + return (callback_map_.find(key) != callback_map_.end()); + } + + bool HasAnyCallbacks() const { + return !callback_map_.empty(); + } + + iterator Begin() { return callback_map_.begin(); } + iterator End() { return callback_map_.end(); } + + void Clear() { callback_map_.clear(); } + + // Runs the callbacks added for the given |key| and clears the key + // from the map. + void Run(const Key& key, const Args& args) { + if (!this->HasCallbacks(key)) + return; + CallbackQueueType& queue = callback_map_[key]; + queue.Run(args); + callback_map_.erase(key); + } + + private: + CallbackMap callback_map_; +}; + +typedef CallbackQueueMap<UsageCallback, std::string, Tuple1<int64> > + HostUsageCallbackMap; +typedef CallbackQueueMap<QuotaCallback, std::string, + Tuple2<QuotaStatusCode, int64> > + HostQuotaCallbackMap; + +} // namespace quota + +#endif // WEBKIT_QUOTA_QUOTA_TYPES_H_ diff --git a/webkit/browser/quota/quota_client.h b/webkit/browser/quota/quota_client.h new file mode 100644 index 0000000..69b95db --- /dev/null +++ b/webkit/browser/quota/quota_client.h @@ -0,0 +1,79 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_QUOTA_CLIENT_H_ +#define WEBKIT_BROWSER_QUOTA_QUOTA_CLIENT_H_ + +#include <list> +#include <set> +#include <string> + +#include "base/callback.h" +#include "googleurl/src/gurl.h" +#include "webkit/common/quota/quota_types.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace quota { + +// An abstract interface for quota manager clients. +// Each storage API must provide an implementation of this interface and +// register it to the quota manager. +// All the methods are assumed to be called on the IO thread in the browser. +class WEBKIT_STORAGE_EXPORT QuotaClient { + public: + typedef base::Callback<void(int64 usage)> GetUsageCallback; + typedef base::Callback<void(const std::set<GURL>& origins)> + GetOriginsCallback; + typedef base::Callback<void(QuotaStatusCode status)> DeletionCallback; + + virtual ~QuotaClient() {} + + enum ID { + kUnknown = 1 << 0, + kFileSystem = 1 << 1, + kDatabase = 1 << 2, + kAppcache = 1 << 3, + kIndexedDatabase = 1 << 4, + kAllClientsMask = -1, + }; + + virtual ID id() const = 0; + + // Called when the quota manager is destroyed. + virtual void OnQuotaManagerDestroyed() = 0; + + // Called by the QuotaManager. + // Gets the amount of data stored in the storage specified by + // |origin_url| and |type|. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void GetOriginUsage(const GURL& origin_url, + StorageType type, + const GetUsageCallback& callback) = 0; + + // Called by the QuotaManager. + // Returns a list of origins that has data in the |type| storage. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void GetOriginsForType(StorageType type, + const GetOriginsCallback& callback) = 0; + + // Called by the QuotaManager. + // Returns a list of origins that match the |host|. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void GetOriginsForHost(StorageType type, + const std::string& host, + const GetOriginsCallback& callback) = 0; + + // Called by the QuotaManager. + // Note it is safe to fire the callback after the QuotaClient is destructed. + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + const DeletionCallback& callback) = 0; +}; + +// TODO(dmikurube): Replace it to std::vector for efficiency. +typedef std::list<QuotaClient*> QuotaClientList; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_QUOTA_CLIENT_H_ diff --git a/webkit/browser/quota/quota_database.cc b/webkit/browser/quota/quota_database.cc new file mode 100644 index 0000000..550f6c3 --- /dev/null +++ b/webkit/browser/quota/quota_database.cc @@ -0,0 +1,661 @@ +// Copyright 2013 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 "webkit/browser/quota/quota_database.h" + +#include <string> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/file_util.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "webkit/browser/quota/special_storage_policy.h" + +namespace quota { +namespace { + +// Definitions for database schema. + +const int kCurrentVersion = 4; +const int kCompatibleVersion = 2; + +const char kHostQuotaTable[] = "HostQuotaTable"; +const char kOriginInfoTable[] = "OriginInfoTable"; +const char kIsOriginTableBootstrapped[] = "IsOriginTableBootstrapped"; + +bool VerifyValidQuotaConfig(const char* key) { + return (key != NULL && + (!strcmp(key, QuotaDatabase::kDesiredAvailableSpaceKey) || + !strcmp(key, QuotaDatabase::kTemporaryQuotaOverrideKey))); +} + +const int kCommitIntervalMs = 30000; + +} // anonymous namespace + +// static +const char QuotaDatabase::kDesiredAvailableSpaceKey[] = "DesiredAvailableSpace"; +const char QuotaDatabase::kTemporaryQuotaOverrideKey[] = + "TemporaryQuotaOverride"; + +const QuotaDatabase::TableSchema QuotaDatabase::kTables[] = { + { kHostQuotaTable, + "(host TEXT NOT NULL," + " type INTEGER NOT NULL," + " quota INTEGER DEFAULT 0," + " UNIQUE(host, type))" }, + { kOriginInfoTable, + "(origin TEXT NOT NULL," + " type INTEGER NOT NULL," + " used_count INTEGER DEFAULT 0," + " last_access_time INTEGER DEFAULT 0," + " last_modified_time INTEGER DEFAULT 0," + " UNIQUE(origin, type))" }, +}; + +// static +const QuotaDatabase::IndexSchema QuotaDatabase::kIndexes[] = { + { "HostIndex", + kHostQuotaTable, + "(host)", + false }, + { "OriginInfoIndex", + kOriginInfoTable, + "(origin)", + false }, + { "OriginLastAccessTimeIndex", + kOriginInfoTable, + "(last_access_time)", + false }, + { "OriginLastModifiedTimeIndex", + kOriginInfoTable, + "(last_modified_time)", + false }, +}; + +struct QuotaDatabase::QuotaTableImporter { + bool Append(const QuotaTableEntry& entry) { + entries.push_back(entry); + return true; + } + std::vector<QuotaTableEntry> entries; +}; + +// Clang requires explicit out-of-line constructors for them. +QuotaDatabase::QuotaTableEntry::QuotaTableEntry() + : type(kStorageTypeUnknown), + quota(0) { +} + +QuotaDatabase::QuotaTableEntry::QuotaTableEntry( + const std::string& host, + StorageType type, + int64 quota) + : host(host), + type(type), + quota(quota) { +} + +QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry() + : type(kStorageTypeUnknown), + used_count(0) { +} + +QuotaDatabase::OriginInfoTableEntry::OriginInfoTableEntry( + const GURL& origin, + StorageType type, + int used_count, + const base::Time& last_access_time, + const base::Time& last_modified_time) + : origin(origin), + type(type), + used_count(used_count), + last_access_time(last_access_time), + last_modified_time(last_modified_time) { +} + +// QuotaDatabase ------------------------------------------------------------ +QuotaDatabase::QuotaDatabase(const base::FilePath& path) + : db_file_path_(path), + is_recreating_(false), + is_disabled_(false) { +} + +QuotaDatabase::~QuotaDatabase() { + if (db_) { + db_->CommitTransaction(); + } +} + +void QuotaDatabase::CloseConnection() { + meta_table_.reset(); + db_.reset(); +} + +bool QuotaDatabase::GetHostQuota( + const std::string& host, StorageType type, int64* quota) { + DCHECK(quota); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT quota" + " FROM HostQuotaTable" + " WHERE host = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, host); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Step()) + return false; + + *quota = statement.ColumnInt64(0); + return true; +} + +bool QuotaDatabase::SetHostQuota( + const std::string& host, StorageType type, int64 quota) { + DCHECK_GE(quota, 0); + if (!LazyOpen(true)) + return false; + + const char* kSql = + "INSERT OR REPLACE INTO HostQuotaTable" + " (quota, host, type)" + " VALUES (?, ?, ?)"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt64(0, quota); + statement.BindString(1, host); + statement.BindInt(2, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::SetOriginLastAccessTime( + const GURL& origin, StorageType type, base::Time last_access_time) { + if (!LazyOpen(true)) + return false; + + sql::Statement statement; + + int used_count = 1; + if (FindOriginUsedCount(origin, type, &used_count)) { + ++used_count; + const char* kSql = + "UPDATE OriginInfoTable" + " SET used_count = ?, last_access_time = ?" + " WHERE origin = ? AND type = ?"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } else { + const char* kSql = + "INSERT INTO OriginInfoTable" + " (used_count, last_access_time, origin, type)" + " VALUES (?, ?, ?, ?)"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } + statement.BindInt(0, used_count); + statement.BindInt64(1, last_access_time.ToInternalValue()); + statement.BindString(2, origin.spec()); + statement.BindInt(3, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::SetOriginLastModifiedTime( + const GURL& origin, StorageType type, base::Time last_modified_time) { + if (!LazyOpen(true)) + return false; + + sql::Statement statement; + + int dummy; + if (FindOriginUsedCount(origin, type, &dummy)) { + const char* kSql = + "UPDATE OriginInfoTable" + " SET last_modified_time = ?" + " WHERE origin = ? AND type = ?"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } else { + const char* kSql = + "INSERT INTO OriginInfoTable" + " (last_modified_time, origin, type) VALUES (?, ?, ?)"; + statement.Assign(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + } + statement.BindInt64(0, last_modified_time.ToInternalValue()); + statement.BindString(1, origin.spec()); + statement.BindInt(2, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::RegisterInitialOriginInfo( + const std::set<GURL>& origins, StorageType type) { + if (!LazyOpen(true)) + return false; + + typedef std::set<GURL>::const_iterator itr_type; + for (itr_type itr = origins.begin(), end = origins.end(); + itr != end; ++itr) { + const char* kSql = + "INSERT OR IGNORE INTO OriginInfoTable" + " (origin, type) VALUES (?, ?)"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, itr->spec()); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Run()) + return false; + } + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::DeleteHostQuota( + const std::string& host, StorageType type) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM HostQuotaTable" + " WHERE host = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, host); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::DeleteOriginInfo( + const GURL& origin, StorageType type) { + if (!LazyOpen(false)) + return false; + + const char* kSql = + "DELETE FROM OriginInfoTable" + " WHERE origin = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, origin.spec()); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Run()) + return false; + + ScheduleCommit(); + return true; +} + +bool QuotaDatabase::GetQuotaConfigValue(const char* key, int64* value) { + if (!LazyOpen(false)) + return false; + DCHECK(VerifyValidQuotaConfig(key)); + return meta_table_->GetValue(key, value); +} + +bool QuotaDatabase::SetQuotaConfigValue(const char* key, int64 value) { + if (!LazyOpen(true)) + return false; + DCHECK(VerifyValidQuotaConfig(key)); + return meta_table_->SetValue(key, value); +} + +bool QuotaDatabase::GetLRUOrigin( + StorageType type, + const std::set<GURL>& exceptions, + SpecialStoragePolicy* special_storage_policy, + GURL* origin) { + DCHECK(origin); + if (!LazyOpen(false)) + return false; + + const char* kSql = "SELECT origin FROM OriginInfoTable" + " WHERE type = ?" + " ORDER BY last_access_time ASC"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt(0, static_cast<int>(type)); + + while (statement.Step()) { + GURL url(statement.ColumnString(0)); + if (exceptions.find(url) != exceptions.end()) + continue; + if (special_storage_policy && + special_storage_policy->IsStorageUnlimited(url)) + continue; + *origin = url; + return true; + } + + *origin = GURL(); + return statement.Succeeded(); +} + +bool QuotaDatabase::GetOriginsModifiedSince( + StorageType type, std::set<GURL>* origins, base::Time modified_since) { + DCHECK(origins); + if (!LazyOpen(false)) + return false; + + const char* kSql = "SELECT origin FROM OriginInfoTable" + " WHERE type = ? AND last_modified_time >= ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindInt(0, static_cast<int>(type)); + statement.BindInt64(1, modified_since.ToInternalValue()); + + origins->clear(); + while (statement.Step()) + origins->insert(GURL(statement.ColumnString(0))); + + return statement.Succeeded(); +} + +bool QuotaDatabase::IsOriginDatabaseBootstrapped() { + if (!LazyOpen(true)) + return false; + + int flag = 0; + return meta_table_->GetValue(kIsOriginTableBootstrapped, &flag) && flag; +} + +bool QuotaDatabase::SetOriginDatabaseBootstrapped(bool bootstrap_flag) { + if (!LazyOpen(true)) + return false; + + return meta_table_->SetValue(kIsOriginTableBootstrapped, bootstrap_flag); +} + +void QuotaDatabase::Commit() { + if (!db_) + return; + + if (timer_.IsRunning()) + timer_.Stop(); + + db_->CommitTransaction(); + db_->BeginTransaction(); +} + +void QuotaDatabase::ScheduleCommit() { + if (timer_.IsRunning()) + return; + timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kCommitIntervalMs), + this, &QuotaDatabase::Commit); +} + +bool QuotaDatabase::FindOriginUsedCount( + const GURL& origin, StorageType type, int* used_count) { + DCHECK(used_count); + if (!LazyOpen(false)) + return false; + + const char* kSql = + "SELECT used_count FROM OriginInfoTable" + " WHERE origin = ? AND type = ?"; + + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + statement.BindString(0, origin.spec()); + statement.BindInt(1, static_cast<int>(type)); + + if (!statement.Step()) + return false; + + *used_count = statement.ColumnInt(0); + return true; +} + +bool QuotaDatabase::LazyOpen(bool create_if_needed) { + if (db_) + return true; + + // If we tried and failed once, don't try again in the same session + // to avoid creating an incoherent mess on disk. + if (is_disabled_) + return false; + + bool in_memory_only = db_file_path_.empty(); + if (!create_if_needed && + (in_memory_only || !file_util::PathExists(db_file_path_))) { + return false; + } + + db_.reset(new sql::Connection); + meta_table_.reset(new sql::MetaTable); + + db_->set_histogram_tag("Quota"); + + bool opened = false; + if (in_memory_only) { + opened = db_->OpenInMemory(); + } else if (!file_util::CreateDirectory(db_file_path_.DirName())) { + LOG(ERROR) << "Failed to create quota database directory."; + } else { + opened = db_->Open(db_file_path_); + if (opened) + db_->Preload(); + } + + if (!opened || !EnsureDatabaseVersion()) { + LOG(ERROR) << "Failed to open the quota database."; + is_disabled_ = true; + db_.reset(); + meta_table_.reset(); + return false; + } + + // Start a long-running transaction. + db_->BeginTransaction(); + + return true; +} + +bool QuotaDatabase::EnsureDatabaseVersion() { + static const size_t kTableCount = ARRAYSIZE_UNSAFE(kTables); + static const size_t kIndexCount = ARRAYSIZE_UNSAFE(kIndexes); + if (!sql::MetaTable::DoesTableExist(db_.get())) + return CreateSchema(db_.get(), meta_table_.get(), + kCurrentVersion, kCompatibleVersion, + kTables, kTableCount, + kIndexes, kIndexCount); + + if (!meta_table_->Init(db_.get(), kCurrentVersion, kCompatibleVersion)) + return false; + + if (meta_table_->GetCompatibleVersionNumber() > kCurrentVersion) { + LOG(WARNING) << "Quota database is too new."; + return false; + } + + if (meta_table_->GetVersionNumber() < kCurrentVersion) { + if (!UpgradeSchema(meta_table_->GetVersionNumber())) + return ResetSchema(); + } + +#ifndef NDEBUG + DCHECK(sql::MetaTable::DoesTableExist(db_.get())); + for (size_t i = 0; i < kTableCount; ++i) { + DCHECK(db_->DoesTableExist(kTables[i].table_name)); + } +#endif + + return true; +} + +// static +bool QuotaDatabase::CreateSchema( + sql::Connection* database, + sql::MetaTable* meta_table, + int schema_version, int compatible_version, + const TableSchema* tables, size_t tables_size, + const IndexSchema* indexes, size_t indexes_size) { + // TODO(kinuko): Factor out the common code to create databases. + sql::Transaction transaction(database); + if (!transaction.Begin()) + return false; + + if (!meta_table->Init(database, schema_version, compatible_version)) + return false; + + for (size_t i = 0; i < tables_size; ++i) { + std::string sql("CREATE TABLE "); + sql += tables[i].table_name; + sql += tables[i].columns; + if (!database->Execute(sql.c_str())) { + VLOG(1) << "Failed to execute " << sql; + return false; + } + } + + for (size_t i = 0; i < indexes_size; ++i) { + std::string sql; + if (indexes[i].unique) + sql += "CREATE UNIQUE INDEX "; + else + sql += "CREATE INDEX "; + sql += indexes[i].index_name; + sql += " ON "; + sql += indexes[i].table_name; + sql += indexes[i].columns; + if (!database->Execute(sql.c_str())) { + VLOG(1) << "Failed to execute " << sql; + return false; + } + } + + return transaction.Commit(); +} + +bool QuotaDatabase::ResetSchema() { + DCHECK(!db_file_path_.empty()); + DCHECK(file_util::PathExists(db_file_path_)); + VLOG(1) << "Deleting existing quota data and starting over."; + + db_.reset(); + meta_table_.reset(); + + if (!file_util::Delete(db_file_path_, true)) + return false; + + // Make sure the steps above actually deleted things. + if (file_util::PathExists(db_file_path_)) + return false; + + // So we can't go recursive. + if (is_recreating_) + return false; + + base::AutoReset<bool> auto_reset(&is_recreating_, true); + return LazyOpen(true); +} + +bool QuotaDatabase::UpgradeSchema(int current_version) { + if (current_version == 2) { + QuotaTableImporter importer; + typedef std::vector<QuotaTableEntry> QuotaTableEntries; + if (!DumpQuotaTable(new QuotaTableCallback(base::Bind( + &QuotaTableImporter::Append, base::Unretained(&importer))))) + return false; + ResetSchema(); + for (QuotaTableEntries::const_iterator iter = importer.entries.begin(); + iter != importer.entries.end(); ++iter) { + if (!SetHostQuota(iter->host, iter->type, iter->quota)) + return false; + } + Commit(); + return true; + } + return false; +} + +bool QuotaDatabase::DumpQuotaTable(QuotaTableCallback* callback) { + scoped_ptr<QuotaTableCallback> callback_deleter(callback); + if (!LazyOpen(true)) + return false; + + const char* kSql = "SELECT * FROM HostQuotaTable"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + + while (statement.Step()) { + QuotaTableEntry entry = QuotaTableEntry( + statement.ColumnString(0), + static_cast<StorageType>(statement.ColumnInt(1)), + statement.ColumnInt64(2)); + + if (!callback->Run(entry)) + return true; + } + + return statement.Succeeded(); +} + +bool QuotaDatabase::DumpOriginInfoTable( + OriginInfoTableCallback* callback) { + scoped_ptr<OriginInfoTableCallback> callback_deleter(callback); + + if (!LazyOpen(true)) + return false; + + const char* kSql = "SELECT * FROM OriginInfoTable"; + sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSql)); + + while (statement.Step()) { + OriginInfoTableEntry entry( + GURL(statement.ColumnString(0)), + static_cast<StorageType>(statement.ColumnInt(1)), + statement.ColumnInt(2), + base::Time::FromInternalValue(statement.ColumnInt64(3)), + base::Time::FromInternalValue(statement.ColumnInt64(4))); + + if (!callback->Run(entry)) + return true; + } + + return statement.Succeeded(); +} + +bool operator<(const QuotaDatabase::QuotaTableEntry& lhs, + const QuotaDatabase::QuotaTableEntry& rhs) { + if (lhs.host < rhs.host) return true; + if (rhs.host < lhs.host) return false; + if (lhs.type < rhs.type) return true; + if (rhs.type < lhs.type) return false; + return lhs.quota < rhs.quota; +} + +bool operator<(const QuotaDatabase::OriginInfoTableEntry& lhs, + const QuotaDatabase::OriginInfoTableEntry& rhs) { + if (lhs.origin < rhs.origin) return true; + if (rhs.origin < lhs.origin) return false; + if (lhs.type < rhs.type) return true; + if (rhs.type < lhs.type) return false; + if (lhs.used_count < rhs.used_count) return true; + if (rhs.used_count < lhs.used_count) return false; + return lhs.last_access_time < rhs.last_access_time; +} + +} // quota namespace diff --git a/webkit/browser/quota/quota_database.h b/webkit/browser/quota/quota_database.h new file mode 100644 index 0000000..ad9d5ad --- /dev/null +++ b/webkit/browser/quota/quota_database.h @@ -0,0 +1,186 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_QUOTA_DATABASE_H_ +#define WEBKIT_BROWSER_QUOTA_QUOTA_DATABASE_H_ + +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" +#include "base/timer.h" +#include "googleurl/src/gurl.h" +#include "webkit/common/quota/quota_types.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace sql { +class Connection; +class MetaTable; +} + +class GURL; + +namespace quota { + +class SpecialStoragePolicy; + +// All the methods of this class must run on the DB thread. +class WEBKIT_STORAGE_EXPORT_PRIVATE QuotaDatabase { + public: + // Constants for {Get,Set}QuotaConfigValue keys. + static const char kDesiredAvailableSpaceKey[]; + static const char kTemporaryQuotaOverrideKey[]; + + // If 'path' is empty, an in memory database will be used. + explicit QuotaDatabase(const base::FilePath& path); + ~QuotaDatabase(); + + void CloseConnection(); + + bool GetHostQuota(const std::string& host, StorageType type, int64* quota); + bool SetHostQuota(const std::string& host, StorageType type, int64 quota); + bool DeleteHostQuota(const std::string& host, StorageType type); + + bool SetOriginLastAccessTime(const GURL& origin, + StorageType type, + base::Time last_access_time); + + bool SetOriginLastModifiedTime(const GURL& origin, + StorageType type, + base::Time last_modified_time); + + // Register initial |origins| info |type| to the database. + // This method is assumed to be called only after the installation or + // the database schema reset. + bool RegisterInitialOriginInfo( + const std::set<GURL>& origins, StorageType type); + + bool DeleteOriginInfo(const GURL& origin, StorageType type); + + bool GetQuotaConfigValue(const char* key, int64* value); + bool SetQuotaConfigValue(const char* key, int64 value); + + // Sets |origin| to the least recently used origin of origins not included + // in |exceptions| and not granted the special unlimited storage right. + // It returns false when it failed in accessing the database. + // |origin| is set to empty when there is no matching origin. + bool GetLRUOrigin(StorageType type, + const std::set<GURL>& exceptions, + SpecialStoragePolicy* special_storage_policy, + GURL* origin); + + // Populates |origins| with the ones that have been modified since + // the |modified_since|. + bool GetOriginsModifiedSince(StorageType type, + std::set<GURL>* origins, + base::Time modified_since); + + // Returns false if SetOriginDatabaseBootstrapped has never + // been called before, which means existing origins may not have been + // registered. + bool IsOriginDatabaseBootstrapped(); + bool SetOriginDatabaseBootstrapped(bool bootstrap_flag); + + private: + struct WEBKIT_STORAGE_EXPORT_PRIVATE QuotaTableEntry { + QuotaTableEntry(); + QuotaTableEntry( + const std::string& host, + StorageType type, + int64 quota); + std::string host; + StorageType type; + int64 quota; + }; + friend WEBKIT_STORAGE_EXPORT_PRIVATE bool operator <( + const QuotaTableEntry& lhs, const QuotaTableEntry& rhs); + + struct WEBKIT_STORAGE_EXPORT_PRIVATE OriginInfoTableEntry { + OriginInfoTableEntry(); + OriginInfoTableEntry( + const GURL& origin, + StorageType type, + int used_count, + const base::Time& last_access_time, + const base::Time& last_modified_time); + GURL origin; + StorageType type; + int used_count; + base::Time last_access_time; + base::Time last_modified_time; + }; + friend WEBKIT_STORAGE_EXPORT_PRIVATE bool operator <( + const OriginInfoTableEntry& lhs, const OriginInfoTableEntry& rhs); + + // Structures used for CreateSchema. + struct TableSchema { + const char* table_name; + const char* columns; + }; + struct IndexSchema { + const char* index_name; + const char* table_name; + const char* columns; + bool unique; + }; + + typedef base::Callback<bool (const QuotaTableEntry&)> QuotaTableCallback; + typedef base::Callback<bool (const OriginInfoTableEntry&)> + OriginInfoTableCallback; + + struct QuotaTableImporter; + + // For long-running transactions support. We always keep a transaction open + // so that multiple transactions can be batched. They are flushed + // with a delay after a modification has been made. We support neither + // nested transactions nor rollback (as we don't need them for now). + void Commit(); + void ScheduleCommit(); + + bool FindOriginUsedCount(const GURL& origin, + StorageType type, + int* used_count); + + bool LazyOpen(bool create_if_needed); + bool EnsureDatabaseVersion(); + bool ResetSchema(); + bool UpgradeSchema(int current_version); + + static bool CreateSchema( + sql::Connection* database, + sql::MetaTable* meta_table, + int schema_version, int compatible_version, + const TableSchema* tables, size_t tables_size, + const IndexSchema* indexes, size_t indexes_size); + + // |callback| may return false to stop reading data. + bool DumpQuotaTable(QuotaTableCallback* callback); + bool DumpOriginInfoTable(OriginInfoTableCallback* callback); + + base::FilePath db_file_path_; + + scoped_ptr<sql::Connection> db_; + scoped_ptr<sql::MetaTable> meta_table_; + bool is_recreating_; + bool is_disabled_; + + base::OneShotTimer<QuotaDatabase> timer_; + + friend class QuotaDatabaseTest; + friend class QuotaManager; + + static const TableSchema kTables[]; + static const IndexSchema kIndexes[]; + + DISALLOW_COPY_AND_ASSIGN(QuotaDatabase); +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_QUOTA_DATABASE_H_ diff --git a/webkit/browser/quota/quota_database_unittest.cc b/webkit/browser/quota/quota_database_unittest.cc new file mode 100644 index 0000000..e9f2760 --- /dev/null +++ b/webkit/browser/quota/quota_database_unittest.cc @@ -0,0 +1,579 @@ +// Copyright 2013 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 <iterator> +#include <set> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/message_loop.h" +#include "googleurl/src/gurl.h" +#include "sql/connection.h" +#include "sql/meta_table.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/quota_database.h" + +namespace quota { +namespace { + +const base::Time kZeroTime; + +class TestErrorDelegate : public sql::ErrorDelegate { + public: + virtual int OnError(int error, + sql::Connection* connection, + sql::Statement* stmt) OVERRIDE { + return error; + } + + protected: + virtual ~TestErrorDelegate() {} +}; + +} // namespace + +class QuotaDatabaseTest : public testing::Test { + protected: + typedef QuotaDatabase::QuotaTableEntry QuotaTableEntry; + typedef QuotaDatabase::QuotaTableCallback QuotaTableCallback; + typedef QuotaDatabase::OriginInfoTableCallback + OriginInfoTableCallback; + + void LazyOpen(const base::FilePath& kDbFile) { + QuotaDatabase db(kDbFile); + EXPECT_FALSE(db.LazyOpen(false)); + ASSERT_TRUE(db.LazyOpen(true)); + EXPECT_TRUE(db.db_.get()); + EXPECT_TRUE(kDbFile.empty() || file_util::PathExists(kDbFile)); + } + + void UpgradeSchemaV2toV3(const base::FilePath& kDbFile) { + const QuotaTableEntry entries[] = { + QuotaTableEntry("a", kStorageTypeTemporary, 1), + QuotaTableEntry("b", kStorageTypeTemporary, 2), + QuotaTableEntry("c", kStorageTypePersistent, 3), + }; + + CreateV2Database(kDbFile, entries, ARRAYSIZE_UNSAFE(entries)); + + QuotaDatabase db(kDbFile); + EXPECT_TRUE(db.LazyOpen(true)); + EXPECT_TRUE(db.db_.get()); + + typedef EntryVerifier<QuotaTableEntry> Verifier; + Verifier verifier(entries, entries + ARRAYSIZE_UNSAFE(entries)); + EXPECT_TRUE(db.DumpQuotaTable( + new QuotaTableCallback( + base::Bind(&Verifier::Run, + base::Unretained(&verifier))))); + EXPECT_TRUE(verifier.table.empty()); + } + + void HostQuota(const base::FilePath& kDbFile) { + QuotaDatabase db(kDbFile); + ASSERT_TRUE(db.LazyOpen(true)); + + const char* kHost = "foo.com"; + const int kQuota1 = 13579; + const int kQuota2 = kQuota1 + 1024; + + int64 quota = -1; + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypePersistent, "a)); + + // Insert quota for temporary. + EXPECT_TRUE(db.SetHostQuota(kHost, kStorageTypeTemporary, kQuota1)); + EXPECT_TRUE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + EXPECT_EQ(kQuota1, quota); + + // Update quota for temporary. + EXPECT_TRUE(db.SetHostQuota(kHost, kStorageTypeTemporary, kQuota2)); + EXPECT_TRUE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + EXPECT_EQ(kQuota2, quota); + + // Quota for persistent must not be updated. + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypePersistent, "a)); + + // Delete temporary storage quota. + EXPECT_TRUE(db.DeleteHostQuota(kHost, kStorageTypeTemporary)); + EXPECT_FALSE(db.GetHostQuota(kHost, kStorageTypeTemporary, "a)); + } + + void GlobalQuota(const base::FilePath& kDbFile) { + QuotaDatabase db(kDbFile); + ASSERT_TRUE(db.LazyOpen(true)); + + const char* kTempQuotaKey = QuotaDatabase::kTemporaryQuotaOverrideKey; + const char* kAvailSpaceKey = QuotaDatabase::kDesiredAvailableSpaceKey; + + int64 value = 0; + const int64 kValue1 = 456; + const int64 kValue2 = 123000; + EXPECT_FALSE(db.GetQuotaConfigValue(kTempQuotaKey, &value)); + EXPECT_FALSE(db.GetQuotaConfigValue(kAvailSpaceKey, &value)); + + EXPECT_TRUE(db.SetQuotaConfigValue(kTempQuotaKey, kValue1)); + EXPECT_TRUE(db.GetQuotaConfigValue(kTempQuotaKey, &value)); + EXPECT_EQ(kValue1, value); + + EXPECT_TRUE(db.SetQuotaConfigValue(kTempQuotaKey, kValue2)); + EXPECT_TRUE(db.GetQuotaConfigValue(kTempQuotaKey, &value)); + EXPECT_EQ(kValue2, value); + + EXPECT_TRUE(db.SetQuotaConfigValue(kAvailSpaceKey, kValue1)); + EXPECT_TRUE(db.GetQuotaConfigValue(kAvailSpaceKey, &value)); + EXPECT_EQ(kValue1, value); + + EXPECT_TRUE(db.SetQuotaConfigValue(kAvailSpaceKey, kValue2)); + EXPECT_TRUE(db.GetQuotaConfigValue(kAvailSpaceKey, &value)); + EXPECT_EQ(kValue2, value); + } + + void OriginLastAccessTimeLRU(const base::FilePath& kDbFile) { + QuotaDatabase db(kDbFile); + ASSERT_TRUE(db.LazyOpen(true)); + + std::set<GURL> exceptions; + GURL origin; + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_TRUE(origin.is_empty()); + + const GURL kOrigin1("http://a/"); + const GURL kOrigin2("http://b/"); + const GURL kOrigin3("http://c/"); + const GURL kOrigin4("http://p/"); + + // Adding three temporary storages, and + EXPECT_TRUE(db.SetOriginLastAccessTime( + kOrigin1, kStorageTypeTemporary, base::Time::FromInternalValue(10))); + EXPECT_TRUE(db.SetOriginLastAccessTime( + kOrigin2, kStorageTypeTemporary, base::Time::FromInternalValue(20))); + EXPECT_TRUE(db.SetOriginLastAccessTime( + kOrigin3, kStorageTypeTemporary, base::Time::FromInternalValue(30))); + + // one persistent. + EXPECT_TRUE(db.SetOriginLastAccessTime( + kOrigin4, kStorageTypePersistent, base::Time::FromInternalValue(40))); + + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_EQ(kOrigin1.spec(), origin.spec()); + + // Test that unlimited origins are exluded from eviction, but + // protected origins are not excluded. + scoped_refptr<MockSpecialStoragePolicy> policy( + new MockSpecialStoragePolicy); + policy->AddUnlimited(kOrigin1); + policy->AddProtected(kOrigin2); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + policy, &origin)); + EXPECT_EQ(kOrigin2.spec(), origin.spec()); + + exceptions.insert(kOrigin1); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_EQ(kOrigin2.spec(), origin.spec()); + + exceptions.insert(kOrigin2); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_EQ(kOrigin3.spec(), origin.spec()); + + exceptions.insert(kOrigin3); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_TRUE(origin.is_empty()); + + EXPECT_TRUE(db.SetOriginLastAccessTime( + kOrigin1, kStorageTypeTemporary, base::Time::Now())); + + // Delete origin/type last access time information. + EXPECT_TRUE(db.DeleteOriginInfo(kOrigin3, kStorageTypeTemporary)); + + // Querying again to see if the deletion has worked. + exceptions.clear(); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_EQ(kOrigin2.spec(), origin.spec()); + + exceptions.insert(kOrigin1); + exceptions.insert(kOrigin2); + EXPECT_TRUE(db.GetLRUOrigin(kStorageTypeTemporary, exceptions, + NULL, &origin)); + EXPECT_TRUE(origin.is_empty()); + } + + void OriginLastModifiedSince(const base::FilePath& kDbFile) { + QuotaDatabase db(kDbFile); + ASSERT_TRUE(db.LazyOpen(true)); + + std::set<GURL> origins; + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypeTemporary, &origins, base::Time())); + EXPECT_TRUE(origins.empty()); + + const GURL kOrigin1("http://a/"); + const GURL kOrigin2("http://b/"); + const GURL kOrigin3("http://c/"); + + // Report last mod time for the test origins. + EXPECT_TRUE(db.SetOriginLastModifiedTime( + kOrigin1, kStorageTypeTemporary, base::Time::FromInternalValue(0))); + EXPECT_TRUE(db.SetOriginLastModifiedTime( + kOrigin2, kStorageTypeTemporary, base::Time::FromInternalValue(10))); + EXPECT_TRUE(db.SetOriginLastModifiedTime( + kOrigin3, kStorageTypeTemporary, base::Time::FromInternalValue(20))); + + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypeTemporary, &origins, base::Time())); + EXPECT_EQ(3U, origins.size()); + EXPECT_EQ(1U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(1U, origins.count(kOrigin3)); + + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypeTemporary, &origins, base::Time::FromInternalValue(5))); + EXPECT_EQ(2U, origins.size()); + EXPECT_EQ(0U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(1U, origins.count(kOrigin3)); + + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypeTemporary, &origins, base::Time::FromInternalValue(15))); + EXPECT_EQ(1U, origins.size()); + EXPECT_EQ(0U, origins.count(kOrigin1)); + EXPECT_EQ(0U, origins.count(kOrigin2)); + EXPECT_EQ(1U, origins.count(kOrigin3)); + + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypeTemporary, &origins, base::Time::FromInternalValue(25))); + EXPECT_TRUE(origins.empty()); + + // Update origin1's mod time but for persistent storage. + EXPECT_TRUE(db.SetOriginLastModifiedTime( + kOrigin1, kStorageTypePersistent, base::Time::FromInternalValue(30))); + + // Must have no effects on temporary origins info. + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypeTemporary, &origins, base::Time::FromInternalValue(5))); + EXPECT_EQ(2U, origins.size()); + EXPECT_EQ(0U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(1U, origins.count(kOrigin3)); + + // One more update for persistent origin2. + EXPECT_TRUE(db.SetOriginLastModifiedTime( + kOrigin2, kStorageTypePersistent, base::Time::FromInternalValue(40))); + + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypePersistent, &origins, base::Time::FromInternalValue(25))); + EXPECT_EQ(2U, origins.size()); + EXPECT_EQ(1U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(0U, origins.count(kOrigin3)); + + EXPECT_TRUE(db.GetOriginsModifiedSince( + kStorageTypePersistent, &origins, base::Time::FromInternalValue(35))); + EXPECT_EQ(1U, origins.size()); + EXPECT_EQ(0U, origins.count(kOrigin1)); + EXPECT_EQ(1U, origins.count(kOrigin2)); + EXPECT_EQ(0U, origins.count(kOrigin3)); + } + + void RegisterInitialOriginInfo(const base::FilePath& kDbFile) { + QuotaDatabase db(kDbFile); + + const GURL kOrigins[] = { + GURL("http://a/"), + GURL("http://b/"), + GURL("http://c/") }; + std::set<GURL> origins(kOrigins, kOrigins + ARRAYSIZE_UNSAFE(kOrigins)); + + EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kStorageTypeTemporary)); + + int used_count = -1; + EXPECT_TRUE(db.FindOriginUsedCount(GURL("http://a/"), + kStorageTypeTemporary, + &used_count)); + EXPECT_EQ(0, used_count); + + EXPECT_TRUE(db.SetOriginLastAccessTime( + GURL("http://a/"), kStorageTypeTemporary, + base::Time::FromDoubleT(1.0))); + used_count = -1; + EXPECT_TRUE(db.FindOriginUsedCount(GURL("http://a/"), + kStorageTypeTemporary, + &used_count)); + EXPECT_EQ(1, used_count); + + EXPECT_TRUE(db.RegisterInitialOriginInfo(origins, kStorageTypeTemporary)); + + used_count = -1; + EXPECT_TRUE(db.FindOriginUsedCount(GURL("http://a/"), + kStorageTypeTemporary, + &used_count)); + EXPECT_EQ(1, used_count); + } + + template <typename EntryType> + struct EntryVerifier { + std::set<EntryType> table; + + template <typename Iterator> + EntryVerifier(Iterator itr, Iterator end) + : table(itr, end) {} + + bool Run(const EntryType& entry) { + EXPECT_EQ(1u, table.erase(entry)); + return true; + } + }; + + void DumpQuotaTable(const base::FilePath& kDbFile) { + QuotaTableEntry kTableEntries[] = { + QuotaTableEntry("http://go/", kStorageTypeTemporary, 1), + QuotaTableEntry("http://oo/", kStorageTypeTemporary, 2), + QuotaTableEntry("http://gle/", kStorageTypePersistent, 3) + }; + QuotaTableEntry* begin = kTableEntries; + QuotaTableEntry* end = kTableEntries + ARRAYSIZE_UNSAFE(kTableEntries); + + QuotaDatabase db(kDbFile); + EXPECT_TRUE(db.LazyOpen(true)); + AssignQuotaTable(db.db_.get(), begin, end); + db.Commit(); + + typedef EntryVerifier<QuotaTableEntry> Verifier; + Verifier verifier(begin, end); + EXPECT_TRUE(db.DumpQuotaTable( + new QuotaTableCallback( + base::Bind(&Verifier::Run, + base::Unretained(&verifier))))); + EXPECT_TRUE(verifier.table.empty()); + } + + void DumpOriginInfoTable(const base::FilePath& kDbFile) { + base::Time now(base::Time::Now()); + typedef QuotaDatabase::OriginInfoTableEntry Entry; + Entry kTableEntries[] = { + Entry(GURL("http://go/"), kStorageTypeTemporary, 2147483647, now, now), + Entry(GURL("http://oo/"), kStorageTypeTemporary, 0, now, now), + Entry(GURL("http://gle/"), kStorageTypeTemporary, 1, now, now), + }; + Entry* begin = kTableEntries; + Entry* end = kTableEntries + ARRAYSIZE_UNSAFE(kTableEntries); + + QuotaDatabase db(kDbFile); + EXPECT_TRUE(db.LazyOpen(true)); + AssignOriginInfoTable(db.db_.get(), begin, end); + db.Commit(); + + typedef EntryVerifier<Entry> Verifier; + Verifier verifier(begin, end); + EXPECT_TRUE(db.DumpOriginInfoTable( + new OriginInfoTableCallback( + base::Bind(&Verifier::Run, + base::Unretained(&verifier))))); + EXPECT_TRUE(verifier.table.empty()); + } + + private: + template <typename Iterator> + void AssignQuotaTable(sql::Connection* db, Iterator itr, Iterator end) { + ASSERT_NE(db, (sql::Connection*)NULL); + for (; itr != end; ++itr) { + const char* kSql = + "INSERT INTO HostQuotaTable" + " (host, type, quota)" + " VALUES (?, ?, ?)"; + sql::Statement statement; + statement.Assign(db->GetCachedStatement(SQL_FROM_HERE, kSql)); + ASSERT_TRUE(statement.is_valid()); + + statement.BindString(0, itr->host); + statement.BindInt(1, static_cast<int>(itr->type)); + statement.BindInt64(2, itr->quota); + EXPECT_TRUE(statement.Run()); + } + } + + template <typename Iterator> + void AssignOriginInfoTable(sql::Connection* db, Iterator itr, Iterator end) { + ASSERT_NE(db, (sql::Connection*)NULL); + for (; itr != end; ++itr) { + const char* kSql = + "INSERT INTO OriginInfoTable" + " (origin, type, used_count, last_access_time, last_modified_time)" + " VALUES (?, ?, ?, ?, ?)"; + sql::Statement statement; + statement.Assign(db->GetCachedStatement(SQL_FROM_HERE, kSql)); + ASSERT_TRUE(statement.is_valid()); + + statement.BindString(0, itr->origin.spec()); + statement.BindInt(1, static_cast<int>(itr->type)); + statement.BindInt(2, itr->used_count); + statement.BindInt64(3, itr->last_access_time.ToInternalValue()); + statement.BindInt64(4, itr->last_modified_time.ToInternalValue()); + EXPECT_TRUE(statement.Run()); + } + } + + bool OpenDatabase(sql::Connection* db, const base::FilePath& kDbFile) { + if (kDbFile.empty()) { + return db->OpenInMemory(); + } + if (!file_util::CreateDirectory(kDbFile.DirName())) + return false; + if (!db->Open(kDbFile)) + return false; + db->Preload(); + return true; + } + + // Create V2 database and populate some data. + void CreateV2Database( + const base::FilePath& kDbFile, + const QuotaTableEntry* entries, + size_t entries_size) { + scoped_ptr<sql::Connection> db(new sql::Connection); + scoped_ptr<sql::MetaTable> meta_table(new sql::MetaTable); + + // V2 schema definitions. + static const int kCurrentVersion = 2; + static const int kCompatibleVersion = 2; + static const char kHostQuotaTable[] = "HostQuotaTable"; + static const char kOriginLastAccessTable[] = "OriginLastAccessTable"; + static const QuotaDatabase::TableSchema kTables[] = { + { kHostQuotaTable, + "(host TEXT NOT NULL," + " type INTEGER NOT NULL," + " quota INTEGER," + " UNIQUE(host, type))" }, + { kOriginLastAccessTable, + "(origin TEXT NOT NULL," + " type INTEGER NOT NULL," + " used_count INTEGER," + " last_access_time INTEGER," + " UNIQUE(origin, type))" }, + }; + static const QuotaDatabase::IndexSchema kIndexes[] = { + { "HostIndex", + kHostQuotaTable, + "(host)", + false }, + { "OriginLastAccessIndex", + kOriginLastAccessTable, + "(origin, last_access_time)", + false }, + }; + + ASSERT_TRUE(OpenDatabase(db.get(), kDbFile)); + EXPECT_TRUE(QuotaDatabase::CreateSchema( + db.get(), meta_table.get(), + kCurrentVersion, kCompatibleVersion, + kTables, ARRAYSIZE_UNSAFE(kTables), + kIndexes, ARRAYSIZE_UNSAFE(kIndexes))); + + // V2 and V3 QuotaTable are compatible, so we can simply use + // AssignQuotaTable to poplulate v2 database here. + db->BeginTransaction(); + AssignQuotaTable(db.get(), entries, entries + entries_size); + db->CommitTransaction(); + } + + base::MessageLoop message_loop_; +}; + +TEST_F(QuotaDatabaseTest, LazyOpen) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + LazyOpen(kDbFile); + LazyOpen(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, UpgradeSchema) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + UpgradeSchemaV2toV3(kDbFile); +} + +TEST_F(QuotaDatabaseTest, HostQuota) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + HostQuota(kDbFile); + HostQuota(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, GlobalQuota) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + GlobalQuota(kDbFile); + GlobalQuota(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, OriginLastAccessTimeLRU) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + OriginLastAccessTimeLRU(kDbFile); + OriginLastAccessTimeLRU(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, OriginLastModifiedSince) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + OriginLastModifiedSince(kDbFile); + OriginLastModifiedSince(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, BootstrapFlag) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + QuotaDatabase db(kDbFile); + + EXPECT_FALSE(db.IsOriginDatabaseBootstrapped()); + EXPECT_TRUE(db.SetOriginDatabaseBootstrapped(true)); + EXPECT_TRUE(db.IsOriginDatabaseBootstrapped()); + EXPECT_TRUE(db.SetOriginDatabaseBootstrapped(false)); + EXPECT_FALSE(db.IsOriginDatabaseBootstrapped()); +} + +TEST_F(QuotaDatabaseTest, RegisterInitialOriginInfo) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + RegisterInitialOriginInfo(kDbFile); + RegisterInitialOriginInfo(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, DumpQuotaTable) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + DumpQuotaTable(kDbFile); + DumpQuotaTable(base::FilePath()); +} + +TEST_F(QuotaDatabaseTest, DumpOriginInfoTable) { + base::ScopedTempDir data_dir; + ASSERT_TRUE(data_dir.CreateUniqueTempDir()); + const base::FilePath kDbFile = data_dir.path().AppendASCII("quota_manager.db"); + DumpOriginInfoTable(kDbFile); + DumpOriginInfoTable(base::FilePath()); +} +} // namespace quota diff --git a/webkit/browser/quota/quota_manager.cc b/webkit/browser/quota/quota_manager.cc new file mode 100644 index 0000000..3d3e7d7 --- /dev/null +++ b/webkit/browser/quota/quota_manager.cc @@ -0,0 +1,1668 @@ +// Copyright 2013 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 "webkit/browser/quota/quota_manager.h" + +#include <algorithm> +#include <deque> +#include <functional> +#include <set> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/metrics/histogram.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_number_conversions.h" +#include "base/sys_info.h" +#include "base/task_runner_util.h" +#include "base/time.h" +#include "net/base/net_util.h" +#include "webkit/browser/quota/quota_database.h" +#include "webkit/browser/quota/quota_temporary_storage_evictor.h" +#include "webkit/browser/quota/usage_tracker.h" +#include "webkit/common/quota/quota_types.h" + +#define UMA_HISTOGRAM_MBYTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS( \ + (name), static_cast<int>((sample) / kMBytes), \ + 1, 10 * 1024 * 1024 /* 10TB */, 100) + +namespace quota { + +namespace { + +const int64 kMBytes = 1024 * 1024; +const int kMinutesInMilliSeconds = 60 * 1000; + +const int64 kReportHistogramInterval = 60 * 60 * 1000; // 1 hour +const double kTemporaryQuotaRatioToAvail = 0.5; // 50% + +} // namespace + +// Arbitrary for now, but must be reasonably small so that +// in-memory databases can fit. +// TODO(kinuko): Refer SysInfo::AmountOfPhysicalMemory() to determine this. +const int64 QuotaManager::kIncognitoDefaultQuotaLimit = 100 * kMBytes; + +const int64 QuotaManager::kNoLimit = kint64max; + +const int QuotaManager::kPerHostTemporaryPortion = 5; // 20% + +const char QuotaManager::kDatabaseName[] = "QuotaManager"; + +// Preserve kMinimumPreserveForSystem disk space for system book-keeping +// when returning the quota to unlimited apps/extensions. +// TODO(kinuko): This should be like 10% of the actual disk space. +// For now we simply use a constant as getting the disk size needs +// platform-dependent code. (http://crbug.com/178976) +const int64 QuotaManager::kMinimumPreserveForSystem = 1024 * kMBytes; + +const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3; + +const int QuotaManager::kEvictionIntervalInMilliSeconds = + 30 * kMinutesInMilliSeconds; + +// Heuristics: assuming average cloud server allows a few Gigs storage +// on the server side and the storage needs to be shared for user data +// and by multiple apps. +int64 QuotaManager::kSyncableStorageDefaultHostQuota = 500 * kMBytes; + +namespace { + +void CountOriginType(const std::set<GURL>& origins, + SpecialStoragePolicy* policy, + size_t* protected_origins, + size_t* unlimited_origins) { + DCHECK(protected_origins); + DCHECK(unlimited_origins); + *protected_origins = 0; + *unlimited_origins = 0; + if (!policy) + return; + for (std::set<GURL>::const_iterator itr = origins.begin(); + itr != origins.end(); + ++itr) { + if (policy->IsStorageProtected(*itr)) + ++*protected_origins; + if (policy->IsStorageUnlimited(*itr)) + ++*unlimited_origins; + } +} + +bool SetTemporaryGlobalOverrideQuotaOnDBThread(int64* new_quota, + QuotaDatabase* database) { + DCHECK(database); + if (!database->SetQuotaConfigValue( + QuotaDatabase::kTemporaryQuotaOverrideKey, *new_quota)) { + *new_quota = -1; + return false; + } + return true; +} + +bool GetPersistentHostQuotaOnDBThread(const std::string& host, + int64* quota, + QuotaDatabase* database) { + DCHECK(database); + database->GetHostQuota(host, kStorageTypePersistent, quota); + return true; +} + +bool SetPersistentHostQuotaOnDBThread(const std::string& host, + int64* new_quota, + QuotaDatabase* database) { + DCHECK(database); + if (database->SetHostQuota(host, kStorageTypePersistent, *new_quota)) + return true; + *new_quota = 0; + return false; +} + +bool InitializeOnDBThread(int64* temporary_quota_override, + int64* desired_available_space, + QuotaDatabase* database) { + DCHECK(database); + database->GetQuotaConfigValue(QuotaDatabase::kTemporaryQuotaOverrideKey, + temporary_quota_override); + database->GetQuotaConfigValue(QuotaDatabase::kDesiredAvailableSpaceKey, + desired_available_space); + return true; +} + +bool GetLRUOriginOnDBThread(StorageType type, + std::set<GURL>* exceptions, + SpecialStoragePolicy* policy, + GURL* url, + QuotaDatabase* database) { + DCHECK(database); + database->GetLRUOrigin(type, *exceptions, policy, url); + return true; +} + +bool DeleteOriginInfoOnDBThread(const GURL& origin, + StorageType type, + QuotaDatabase* database) { + DCHECK(database); + return database->DeleteOriginInfo(origin, type); +} + +bool InitializeTemporaryOriginsInfoOnDBThread(const std::set<GURL>* origins, + QuotaDatabase* database) { + DCHECK(database); + if (database->IsOriginDatabaseBootstrapped()) + return true; + + // Register existing origins with 0 last time access. + if (database->RegisterInitialOriginInfo(*origins, kStorageTypeTemporary)) { + database->SetOriginDatabaseBootstrapped(true); + return true; + } + return false; +} + +bool UpdateAccessTimeOnDBThread(const GURL& origin, + StorageType type, + base::Time accessed_time, + QuotaDatabase* database) { + DCHECK(database); + return database->SetOriginLastAccessTime(origin, type, accessed_time); +} + +bool UpdateModifiedTimeOnDBThread(const GURL& origin, + StorageType type, + base::Time modified_time, + QuotaDatabase* database) { + DCHECK(database); + return database->SetOriginLastModifiedTime(origin, type, modified_time); +} + +int64 CallSystemGetAmountOfFreeDiskSpace(const base::FilePath& profile_path) { + // Ensure the profile path exists. + if(!file_util::CreateDirectory(profile_path)) { + LOG(WARNING) << "Create directory failed for path" << profile_path.value(); + return 0; + } + return base::SysInfo::AmountOfFreeDiskSpace(profile_path); +} + +int64 CalculateTemporaryGlobalQuota(int64 global_limited_usage, + int64 available_space) { + DCHECK_GE(global_limited_usage, 0); + int64 avail_space = available_space; + if (avail_space < kint64max - global_limited_usage) { + // We basically calculate the temporary quota by + // [available_space + space_used_for_temp] * kTempQuotaRatio, + // but make sure we'll have no overflow. + avail_space += global_limited_usage; + } + return avail_space * kTemporaryQuotaRatioToAvail; +} + +void DispatchTemporaryGlobalQuotaCallback( + const QuotaCallback& callback, + QuotaStatusCode status, + const UsageAndQuota& usage_and_quota) { + if (status != kQuotaStatusOk) { + callback.Run(status, 0); + return; + } + + callback.Run(status, CalculateTemporaryGlobalQuota( + usage_and_quota.global_limited_usage, + usage_and_quota.available_disk_space)); +} + +int64 CalculateQuotaWithDiskSpace( + int64 available_disk_space, int64 usage, int64 quota) { + if (available_disk_space < QuotaManager::kMinimumPreserveForSystem || + quota < usage) { + // No more space; cap the quota to the current usage. + return usage; + } + + available_disk_space -= QuotaManager::kMinimumPreserveForSystem; + if (available_disk_space < quota - usage) + return available_disk_space + usage; + + return quota; +} + +int64 CalculateTemporaryHostQuota(int64 host_usage, + int64 global_quota, + int64 global_limited_usage) { + DCHECK_GE(global_limited_usage, 0); + int64 host_quota = global_quota / QuotaManager::kPerHostTemporaryPortion; + if (global_limited_usage > global_quota) + host_quota = std::min(host_quota, host_usage); + return host_quota; +} + +void DispatchUsageAndQuotaForWebApps( + StorageType type, + bool is_incognito, + bool is_unlimited, + bool can_query_disk_size, + const QuotaManager::GetUsageAndQuotaCallback& callback, + QuotaStatusCode status, + const UsageAndQuota& usage_and_quota) { + if (status != kQuotaStatusOk) { + callback.Run(status, 0, 0); + return; + } + + int64 usage = usage_and_quota.usage; + int64 quota = usage_and_quota.quota; + + if (type == kStorageTypeTemporary && !is_unlimited) { + quota = CalculateTemporaryHostQuota( + usage, quota, usage_and_quota.global_limited_usage); + } + + if (is_incognito) { + quota = std::min(quota, QuotaManager::kIncognitoDefaultQuotaLimit); + callback.Run(status, usage, quota); + return; + } + + // For apps with unlimited permission or can_query_disk_size is true (and not + // in incognito mode). + // We assume we can expose the actual disk size for them and cap the quota by + // the available disk space. + if (is_unlimited || can_query_disk_size) { + callback.Run( + status, usage, + CalculateQuotaWithDiskSpace( + usage_and_quota.available_disk_space, + usage, quota)); + return; + } + + callback.Run(status, usage, quota); +} + +} // namespace + +UsageAndQuota::UsageAndQuota() + : usage(0), + global_limited_usage(0), + quota(0), + available_disk_space(0) { +} + +UsageAndQuota::UsageAndQuota( + int64 usage, + int64 global_limited_usage, + int64 quota, + int64 available_disk_space) + : usage(usage), + global_limited_usage(global_limited_usage), + quota(quota), + available_disk_space(available_disk_space) { +} + +class UsageAndQuotaCallbackDispatcher + : public QuotaTask, + public base::SupportsWeakPtr<UsageAndQuotaCallbackDispatcher> { + public: + UsageAndQuotaCallbackDispatcher(QuotaManager* manager) + : QuotaTask(manager), + has_usage_(false), + has_global_limited_usage_(false), + has_quota_(false), + has_available_disk_space_(false), + status_(kQuotaStatusUnknown), + usage_and_quota_(-1, -1, -1, -1), + waiting_callbacks_(1) {} + + virtual ~UsageAndQuotaCallbackDispatcher() {} + + void WaitForResults(const QuotaManager::UsageAndQuotaCallback& callback) { + callback_ = callback; + Start(); + } + + void set_usage(int64 usage) { + usage_and_quota_.usage = usage; + has_usage_ = true; + } + + void set_global_limited_usage(int64 global_limited_usage) { + usage_and_quota_.global_limited_usage = global_limited_usage; + has_global_limited_usage_ = true; + } + + void set_quota(int64 quota) { + usage_and_quota_.quota = quota; + has_quota_ = true; + } + + void set_available_disk_space(int64 available_disk_space) { + usage_and_quota_.available_disk_space = available_disk_space; + has_available_disk_space_ = true; + } + + UsageCallback GetHostUsageCallback() { + ++waiting_callbacks_; + has_usage_ = true; + return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetHostUsage, + AsWeakPtr()); + } + + UsageCallback GetGlobalLimitedUsageCallback() { + ++waiting_callbacks_; + has_global_limited_usage_ = true; + return base::Bind( + &UsageAndQuotaCallbackDispatcher::DidGetGlobalLimitedUsage, + AsWeakPtr()); + } + + QuotaCallback GetQuotaCallback() { + ++waiting_callbacks_; + has_quota_ = true; + return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetQuota, + AsWeakPtr()); + } + + QuotaCallback GetAvailableSpaceCallback() { + ++waiting_callbacks_; + has_available_disk_space_ = true; + return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetAvailableSpace, + AsWeakPtr()); + } + + private: + void DidGetHostUsage(int64 usage) { + if (status_ == kQuotaStatusUnknown) + status_ = kQuotaStatusOk; + usage_and_quota_.usage = usage; + CheckCompleted(); + } + + void DidGetGlobalLimitedUsage(int64 limited_usage) { + if (status_ == kQuotaStatusUnknown) + status_ = kQuotaStatusOk; + usage_and_quota_.global_limited_usage = limited_usage; + CheckCompleted(); + } + + void DidGetQuota(QuotaStatusCode status, int64 quota) { + if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk) + status_ = status; + usage_and_quota_.quota = quota; + CheckCompleted(); + } + + void DidGetAvailableSpace(QuotaStatusCode status, int64 space) { + DCHECK_GE(space, 0); + if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk) + status_ = status; + usage_and_quota_.available_disk_space = space; + CheckCompleted(); + } + + virtual void Run() OVERRIDE { + // We initialize waiting_callbacks to 1 so that we won't run + // the completion callback until here even some of the callbacks + // are dispatched synchronously. + CheckCompleted(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(kQuotaErrorAbort, UsageAndQuota()); + DeleteSoon(); + } + + virtual void Completed() OVERRIDE { + DCHECK(!has_usage_ || usage_and_quota_.usage >= 0); + DCHECK(!has_global_limited_usage_ || + usage_and_quota_.global_limited_usage >= 0); + DCHECK(!has_quota_ || usage_and_quota_.quota >= 0); + DCHECK(!has_available_disk_space_ || + usage_and_quota_.available_disk_space >= 0); + + callback_.Run(status_, usage_and_quota_); + DeleteSoon(); + } + + void CheckCompleted() { + if (--waiting_callbacks_ <= 0) + CallCompleted(); + } + + // For sanity checks, they're checked only when DCHECK is on. + bool has_usage_; + bool has_global_limited_usage_; + bool has_quota_; + bool has_available_disk_space_; + + QuotaStatusCode status_; + UsageAndQuota usage_and_quota_; + QuotaManager::UsageAndQuotaCallback callback_; + int waiting_callbacks_; + + DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaCallbackDispatcher); +}; + +class QuotaManager::GetUsageInfoTask : public QuotaTask { + private: + typedef QuotaManager::GetUsageInfoTask self_type; + + public: + GetUsageInfoTask( + QuotaManager* manager, + const GetUsageInfoCallback& callback) + : QuotaTask(manager), + callback_(callback), + weak_factory_(this) { + } + + protected: + virtual void Run() OVERRIDE { + remaining_trackers_ = 3; + // This will populate cached hosts and usage info. + manager()->GetUsageTracker(kStorageTypeTemporary)->GetGlobalUsage( + base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, + weak_factory_.GetWeakPtr(), + kStorageTypeTemporary)); + manager()->GetUsageTracker(kStorageTypePersistent)->GetGlobalUsage( + base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, + weak_factory_.GetWeakPtr(), + kStorageTypePersistent)); + manager()->GetUsageTracker(kStorageTypeSyncable)->GetGlobalUsage( + base::Bind(&GetUsageInfoTask::DidGetGlobalUsage, + weak_factory_.GetWeakPtr(), + kStorageTypeSyncable)); + } + + virtual void Completed() OVERRIDE { + callback_.Run(entries_); + DeleteSoon(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(UsageInfoEntries()); + DeleteSoon(); + } + + private: + void AddEntries(StorageType type, UsageTracker* tracker) { + std::map<std::string, int64> host_usage; + tracker->GetCachedHostsUsage(&host_usage); + for (std::map<std::string, int64>::const_iterator iter = host_usage.begin(); + iter != host_usage.end(); + ++iter) { + entries_.push_back(UsageInfo(iter->first, type, iter->second)); + } + if (--remaining_trackers_ == 0) + CallCompleted(); + } + + void DidGetGlobalUsage(StorageType type, int64, int64) { + AddEntries(type, manager()->GetUsageTracker(type)); + } + + QuotaManager* manager() const { + return static_cast<QuotaManager*>(observer()); + } + + GetUsageInfoCallback callback_; + UsageInfoEntries entries_; + base::WeakPtrFactory<GetUsageInfoTask> weak_factory_; + int remaining_trackers_; + + DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask); +}; + +class QuotaManager::OriginDataDeleter : public QuotaTask { + public: + OriginDataDeleter(QuotaManager* manager, + const GURL& origin, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) + : QuotaTask(manager), + origin_(origin), + type_(type), + quota_client_mask_(quota_client_mask), + error_count_(0), + remaining_clients_(-1), + skipped_clients_(0), + callback_(callback), + weak_factory_(this) {} + + protected: + virtual void Run() OVERRIDE { + error_count_ = 0; + remaining_clients_ = manager()->clients_.size(); + for (QuotaClientList::iterator iter = manager()->clients_.begin(); + iter != manager()->clients_.end(); ++iter) { + if (quota_client_mask_ & (*iter)->id()) { + (*iter)->DeleteOriginData( + origin_, type_, + base::Bind(&OriginDataDeleter::DidDeleteOriginData, + weak_factory_.GetWeakPtr())); + } else { + ++skipped_clients_; + if (--remaining_clients_ == 0) + CallCompleted(); + } + } + } + + virtual void Completed() OVERRIDE { + if (error_count_ == 0) { + // Only remove the entire origin if we didn't skip any client types. + if (skipped_clients_ == 0) + manager()->DeleteOriginFromDatabase(origin_, type_); + callback_.Run(kQuotaStatusOk); + } else { + callback_.Run(kQuotaErrorInvalidModification); + } + DeleteSoon(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(kQuotaErrorAbort); + DeleteSoon(); + } + + void DidDeleteOriginData(QuotaStatusCode status) { + DCHECK_GT(remaining_clients_, 0); + + if (status != kQuotaStatusOk) + ++error_count_; + + if (--remaining_clients_ == 0) + CallCompleted(); + } + + QuotaManager* manager() const { + return static_cast<QuotaManager*>(observer()); + } + + GURL origin_; + StorageType type_; + int quota_client_mask_; + int error_count_; + int remaining_clients_; + int skipped_clients_; + StatusCallback callback_; + + base::WeakPtrFactory<OriginDataDeleter> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter); +}; + +class QuotaManager::HostDataDeleter : public QuotaTask { + public: + HostDataDeleter(QuotaManager* manager, + const std::string& host, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) + : QuotaTask(manager), + host_(host), + type_(type), + quota_client_mask_(quota_client_mask), + error_count_(0), + remaining_clients_(-1), + remaining_deleters_(-1), + callback_(callback), + weak_factory_(this) {} + + protected: + virtual void Run() OVERRIDE { + error_count_ = 0; + remaining_clients_ = manager()->clients_.size(); + for (QuotaClientList::iterator iter = manager()->clients_.begin(); + iter != manager()->clients_.end(); ++iter) { + (*iter)->GetOriginsForHost( + type_, host_, + base::Bind(&HostDataDeleter::DidGetOriginsForHost, + weak_factory_.GetWeakPtr())); + } + } + + virtual void Completed() OVERRIDE { + if (error_count_ == 0) { + callback_.Run(kQuotaStatusOk); + } else { + callback_.Run(kQuotaErrorInvalidModification); + } + DeleteSoon(); + } + + virtual void Aborted() OVERRIDE { + callback_.Run(kQuotaErrorAbort); + DeleteSoon(); + } + + void DidGetOriginsForHost(const std::set<GURL>& origins) { + DCHECK_GT(remaining_clients_, 0); + + origins_.insert(origins.begin(), origins.end()); + + if (--remaining_clients_ == 0) { + if (!origins_.empty()) + ScheduleOriginsDeletion(); + else + CallCompleted(); + } + } + + void ScheduleOriginsDeletion() { + remaining_deleters_ = origins_.size(); + for (std::set<GURL>::const_iterator p = origins_.begin(); + p != origins_.end(); + ++p) { + OriginDataDeleter* deleter = + new OriginDataDeleter( + manager(), *p, type_, quota_client_mask_, + base::Bind(&HostDataDeleter::DidDeleteOriginData, + weak_factory_.GetWeakPtr())); + deleter->Start(); + } + } + + void DidDeleteOriginData(QuotaStatusCode status) { + DCHECK_GT(remaining_deleters_, 0); + + if (status != kQuotaStatusOk) + ++error_count_; + + if (--remaining_deleters_ == 0) + CallCompleted(); + } + + QuotaManager* manager() const { + return static_cast<QuotaManager*>(observer()); + } + + std::string host_; + StorageType type_; + int quota_client_mask_; + std::set<GURL> origins_; + int error_count_; + int remaining_clients_; + int remaining_deleters_; + StatusCallback callback_; + + base::WeakPtrFactory<HostDataDeleter> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(HostDataDeleter); +}; + +class QuotaManager::GetModifiedSinceHelper { + public: + bool GetModifiedSinceOnDBThread(StorageType type, + base::Time modified_since, + QuotaDatabase* database) { + DCHECK(database); + return database->GetOriginsModifiedSince(type, &origins_, modified_since); + } + + void DidGetModifiedSince(QuotaManager* manager, + const GetOriginsCallback& callback, + StorageType type, + bool success) { + if (!manager) { + // The operation was aborted. + callback.Run(std::set<GURL>(), type); + return; + } + manager->DidDatabaseWork(success); + callback.Run(origins_, type); + } + + private: + std::set<GURL> origins_; +}; + +class QuotaManager::DumpQuotaTableHelper { + public: + bool DumpQuotaTableOnDBThread(QuotaDatabase* database) { + DCHECK(database); + return database->DumpQuotaTable( + new TableCallback(base::Bind(&DumpQuotaTableHelper::AppendEntry, + base::Unretained(this)))); + } + + void DidDumpQuotaTable(QuotaManager* manager, + const DumpQuotaTableCallback& callback, + bool success) { + if (!manager) { + // The operation was aborted. + callback.Run(QuotaTableEntries()); + return; + } + manager->DidDatabaseWork(success); + callback.Run(entries_); + } + + private: + typedef QuotaDatabase::QuotaTableCallback TableCallback; + + bool AppendEntry(const QuotaTableEntry& entry) { + entries_.push_back(entry); + return true; + } + + QuotaTableEntries entries_; +}; + +class QuotaManager::DumpOriginInfoTableHelper { + public: + bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) { + DCHECK(database); + return database->DumpOriginInfoTable( + new TableCallback(base::Bind(&DumpOriginInfoTableHelper::AppendEntry, + base::Unretained(this)))); + } + + void DidDumpOriginInfoTable(QuotaManager* manager, + const DumpOriginInfoTableCallback& callback, + bool success) { + if (!manager) { + // The operation was aborted. + callback.Run(OriginInfoTableEntries()); + return; + } + manager->DidDatabaseWork(success); + callback.Run(entries_); + } + + private: + typedef QuotaDatabase::OriginInfoTableCallback TableCallback; + + bool AppendEntry(const OriginInfoTableEntry& entry) { + entries_.push_back(entry); + return true; + } + + OriginInfoTableEntries entries_; +}; + +// QuotaManager --------------------------------------------------------------- + +QuotaManager::QuotaManager(bool is_incognito, + const base::FilePath& profile_path, + base::SingleThreadTaskRunner* io_thread, + base::SequencedTaskRunner* db_thread, + SpecialStoragePolicy* special_storage_policy) + : is_incognito_(is_incognito), + profile_path_(profile_path), + proxy_(new QuotaManagerProxy( + this, io_thread)), + db_disabled_(false), + eviction_disabled_(false), + io_thread_(io_thread), + db_thread_(db_thread), + temporary_quota_initialized_(false), + temporary_quota_override_(-1), + desired_available_space_(-1), + special_storage_policy_(special_storage_policy), + weak_factory_(this), + get_disk_space_fn_(&CallSystemGetAmountOfFreeDiskSpace) { +} + +void QuotaManager::GetUsageInfo(const GetUsageInfoCallback& callback) { + LazyInitialize(); + GetUsageInfoTask* get_usage_info = new GetUsageInfoTask(this, callback); + get_usage_info->Start(); +} + +void QuotaManager::GetUsageAndQuotaForWebApps( + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback) { + if (type != kStorageTypeTemporary && + type != kStorageTypePersistent && + type != kStorageTypeSyncable) { + callback.Run(kQuotaErrorNotSupported, 0, 0); + return; + } + + DCHECK(origin == origin.GetOrigin()); + LazyInitialize(); + + bool unlimited = IsStorageUnlimited(origin, type); + bool can_query_disk_size = CanQueryDiskSize(origin); + + UsageAndQuotaCallbackDispatcher* dispatcher = + new UsageAndQuotaCallbackDispatcher(this); + + UsageAndQuota usage_and_quota; + if (unlimited) { + dispatcher->set_quota(kNoLimit); + } else { + if (type == kStorageTypeTemporary) { + GetUsageTracker(type)->GetGlobalLimitedUsage( + dispatcher->GetGlobalLimitedUsageCallback()); + GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback()); + } else if (type == kStorageTypePersistent) { + GetPersistentHostQuota(net::GetHostOrSpecFromURL(origin), + dispatcher->GetQuotaCallback()); + } else { + dispatcher->set_quota(kSyncableStorageDefaultHostQuota); + } + } + + GetUsageTracker(type)->GetHostUsage(net::GetHostOrSpecFromURL(origin), + dispatcher->GetHostUsageCallback()); + + if (!is_incognito_ && (unlimited || can_query_disk_size)) + GetAvailableSpace(dispatcher->GetAvailableSpaceCallback()); + + dispatcher->WaitForResults(base::Bind( + &DispatchUsageAndQuotaForWebApps, + type, is_incognito_, unlimited, can_query_disk_size, + callback)); +} + +void QuotaManager::GetUsageAndQuota( + const GURL& origin, StorageType type, + const GetUsageAndQuotaCallback& callback) { + DCHECK(origin == origin.GetOrigin()); + + if (IsStorageUnlimited(origin, type)) { + callback.Run(kQuotaStatusOk, 0, kNoLimit); + return; + } + + GetUsageAndQuotaForWebApps(origin, type, callback); +} + +void QuotaManager::NotifyStorageAccessed( + QuotaClient::ID client_id, + const GURL& origin, StorageType type) { + DCHECK(origin == origin.GetOrigin()); + NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now()); +} + +void QuotaManager::NotifyStorageModified( + QuotaClient::ID client_id, + const GURL& origin, StorageType type, int64 delta) { + DCHECK(origin == origin.GetOrigin()); + NotifyStorageModifiedInternal(client_id, origin, type, delta, + base::Time::Now()); +} + +void QuotaManager::NotifyOriginInUse(const GURL& origin) { + DCHECK(io_thread_->BelongsToCurrentThread()); + origins_in_use_[origin]++; +} + +void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) { + DCHECK(io_thread_->BelongsToCurrentThread()); + DCHECK(IsOriginInUse(origin)); + int& count = origins_in_use_[origin]; + if (--count == 0) + origins_in_use_.erase(origin); +} + +void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled) { + LazyInitialize(); + GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled); +} + +void QuotaManager::DeleteOriginData( + const GURL& origin, StorageType type, int quota_client_mask, + const StatusCallback& callback) { + LazyInitialize(); + + if (origin.is_empty() || clients_.empty()) { + callback.Run(kQuotaStatusOk); + return; + } + + DCHECK(origin == origin.GetOrigin()); + OriginDataDeleter* deleter = + new OriginDataDeleter(this, origin, type, quota_client_mask, callback); + deleter->Start(); +} + +void QuotaManager::DeleteHostData(const std::string& host, + StorageType type, + int quota_client_mask, + const StatusCallback& callback) { + LazyInitialize(); + + if (host.empty() || clients_.empty()) { + callback.Run(kQuotaStatusOk); + return; + } + + HostDataDeleter* deleter = + new HostDataDeleter(this, host, type, quota_client_mask, callback); + deleter->Start(); +} + +void QuotaManager::GetAvailableSpace(const AvailableSpaceCallback& callback) { + if (!available_space_callbacks_.Add(callback)) + return; + + PostTaskAndReplyWithResult( + db_thread_, + FROM_HERE, + base::Bind(get_disk_space_fn_, profile_path_), + base::Bind(&QuotaManager::DidGetAvailableSpace, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::GetTemporaryGlobalQuota(const QuotaCallback& callback) { + LazyInitialize(); + if (!temporary_quota_initialized_) { + db_initialization_callbacks_.Add(base::Bind( + &QuotaManager::GetTemporaryGlobalQuota, + weak_factory_.GetWeakPtr(), callback)); + return; + } + + if (temporary_quota_override_ > 0) { + callback.Run(kQuotaStatusOk, temporary_quota_override_); + return; + } + + UsageAndQuotaCallbackDispatcher* dispatcher = + new UsageAndQuotaCallbackDispatcher(this); + GetUsageTracker(kStorageTypeTemporary)-> + GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback()); + GetAvailableSpace(dispatcher->GetAvailableSpaceCallback()); + dispatcher->WaitForResults( + base::Bind(&DispatchTemporaryGlobalQuotaCallback, callback)); +} + +void QuotaManager::SetTemporaryGlobalOverrideQuota( + int64 new_quota, const QuotaCallback& callback) { + LazyInitialize(); + + if (new_quota < 0) { + if (!callback.is_null()) + callback.Run(kQuotaErrorInvalidModification, -1); + return; + } + + if (db_disabled_) { + if (callback.is_null()) + callback.Run(kQuotaErrorInvalidAccess, -1); + return; + } + + int64* new_quota_ptr = new int64(new_quota); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&SetTemporaryGlobalOverrideQuotaOnDBThread, + base::Unretained(new_quota_ptr)), + base::Bind(&QuotaManager::DidSetTemporaryGlobalOverrideQuota, + weak_factory_.GetWeakPtr(), + callback, + base::Owned(new_quota_ptr))); +} + +void QuotaManager::GetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback) { + LazyInitialize(); + if (host.empty()) { + // This could happen if we are called on file:///. + // TODO(kinuko) We may want to respect --allow-file-access-from-files + // command line switch. + callback.Run(kQuotaStatusOk, 0); + return; + } + + if (!persistent_host_quota_callbacks_.Add(host, callback)) + return; + + int64* quota_ptr = new int64(0); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&GetPersistentHostQuotaOnDBThread, + host, + base::Unretained(quota_ptr)), + base::Bind(&QuotaManager::DidGetPersistentHostQuota, + weak_factory_.GetWeakPtr(), + host, + base::Owned(quota_ptr))); +} + +void QuotaManager::SetPersistentHostQuota(const std::string& host, + int64 new_quota, + const QuotaCallback& callback) { + LazyInitialize(); + if (host.empty()) { + // This could happen if we are called on file:///. + callback.Run(kQuotaErrorNotSupported, 0); + return; + } + if (new_quota < 0) { + callback.Run(kQuotaErrorInvalidModification, -1); + return; + } + + if (db_disabled_) { + callback.Run(kQuotaErrorInvalidAccess, -1); + return; + } + + int64* new_quota_ptr = new int64(new_quota); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&SetPersistentHostQuotaOnDBThread, + host, + base::Unretained(new_quota_ptr)), + base::Bind(&QuotaManager::DidSetPersistentHostQuota, + weak_factory_.GetWeakPtr(), + host, + callback, + base::Owned(new_quota_ptr))); +} + +void QuotaManager::GetGlobalUsage(StorageType type, + const GlobalUsageCallback& callback) { + LazyInitialize(); + GetUsageTracker(type)->GetGlobalUsage(callback); +} + +void QuotaManager::GetHostUsage(const std::string& host, + StorageType type, + const UsageCallback& callback) { + LazyInitialize(); + GetUsageTracker(type)->GetHostUsage(host, callback); +} + +void QuotaManager::GetStatistics( + std::map<std::string, std::string>* statistics) { + DCHECK(statistics); + if (temporary_storage_evictor_) { + std::map<std::string, int64> stats; + temporary_storage_evictor_->GetStatistics(&stats); + for (std::map<std::string, int64>::iterator p = stats.begin(); + p != stats.end(); + ++p) + (*statistics)[p->first] = base::Int64ToString(p->second); + } +} + +bool QuotaManager::IsStorageUnlimited(const GURL& origin, + StorageType type) const { + // For syncable storage we should always enforce quota (since the + // quota must be capped by the server limit). + if (type == kStorageTypeSyncable) + return false; + return special_storage_policy_.get() && + special_storage_policy_->IsStorageUnlimited(origin); +} + +void QuotaManager::GetOriginsModifiedSince(StorageType type, + base::Time modified_since, + const GetOriginsCallback& callback) { + LazyInitialize(); + GetModifiedSinceHelper* helper = new GetModifiedSinceHelper; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread, + base::Unretained(helper), + type, + modified_since), + base::Bind(&GetModifiedSinceHelper::DidGetModifiedSince, + base::Owned(helper), + weak_factory_.GetWeakPtr(), + callback, + type)); +} + +bool QuotaManager::ResetUsageTracker(StorageType type) { + DCHECK(GetUsageTracker(type)); + if (GetUsageTracker(type)->IsWorking()) + return false; + switch (type) { + case kStorageTypeTemporary: + temporary_usage_tracker_.reset( + new UsageTracker(clients_, kStorageTypeTemporary, + special_storage_policy_)); + return true; + case kStorageTypePersistent: + persistent_usage_tracker_.reset( + new UsageTracker(clients_, kStorageTypePersistent, + special_storage_policy_)); + return true; + case kStorageTypeSyncable: + syncable_usage_tracker_.reset( + new UsageTracker(clients_, kStorageTypeSyncable, + special_storage_policy_)); + return true; + default: + NOTREACHED(); + } + return true; +} + +QuotaManager::~QuotaManager() { + proxy_->manager_ = NULL; + std::for_each(clients_.begin(), clients_.end(), + std::mem_fun(&QuotaClient::OnQuotaManagerDestroyed)); + if (database_) + db_thread_->DeleteSoon(FROM_HERE, database_.release()); +} + +QuotaManager::EvictionContext::EvictionContext() + : evicted_type(kStorageTypeUnknown) { +} + +QuotaManager::EvictionContext::~EvictionContext() { +} + +void QuotaManager::LazyInitialize() { + DCHECK(io_thread_->BelongsToCurrentThread()); + if (database_) { + // Initialization seems to be done already. + return; + } + + // Use an empty path to open an in-memory only databse for incognito. + database_.reset(new QuotaDatabase(is_incognito_ ? base::FilePath() : + profile_path_.AppendASCII(kDatabaseName))); + + temporary_usage_tracker_.reset( + new UsageTracker(clients_, kStorageTypeTemporary, + special_storage_policy_)); + persistent_usage_tracker_.reset( + new UsageTracker(clients_, kStorageTypePersistent, + special_storage_policy_)); + syncable_usage_tracker_.reset( + new UsageTracker(clients_, kStorageTypeSyncable, + special_storage_policy_)); + + int64* temporary_quota_override = new int64(-1); + int64* desired_available_space = new int64(-1); + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&InitializeOnDBThread, + base::Unretained(temporary_quota_override), + base::Unretained(desired_available_space)), + base::Bind(&QuotaManager::DidInitialize, + weak_factory_.GetWeakPtr(), + base::Owned(temporary_quota_override), + base::Owned(desired_available_space))); +} + +void QuotaManager::RegisterClient(QuotaClient* client) { + DCHECK(!database_.get()); + clients_.push_back(client); +} + +UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const { + switch (type) { + case kStorageTypeTemporary: + return temporary_usage_tracker_.get(); + case kStorageTypePersistent: + return persistent_usage_tracker_.get(); + case kStorageTypeSyncable: + return syncable_usage_tracker_.get(); + default: + NOTREACHED(); + } + return NULL; +} + +void QuotaManager::GetCachedOrigins( + StorageType type, std::set<GURL>* origins) { + DCHECK(origins); + LazyInitialize(); + DCHECK(GetUsageTracker(type)); + GetUsageTracker(type)->GetCachedOrigins(origins); +} + +void QuotaManager::NotifyStorageAccessedInternal( + QuotaClient::ID client_id, + const GURL& origin, StorageType type, + base::Time accessed_time) { + LazyInitialize(); + if (type == kStorageTypeTemporary && !lru_origin_callback_.is_null()) { + // Record the accessed origins while GetLRUOrigin task is runing + // to filter out them from eviction. + access_notified_origins_.insert(origin); + } + + if (db_disabled_) + return; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&UpdateAccessTimeOnDBThread, origin, type, accessed_time), + base::Bind(&QuotaManager::DidDatabaseWork, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::NotifyStorageModifiedInternal( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta, + base::Time modified_time) { + LazyInitialize(); + GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta); + + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&UpdateModifiedTimeOnDBThread, origin, type, modified_time), + base::Bind(&QuotaManager::DidDatabaseWork, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DumpQuotaTable(const DumpQuotaTableCallback& callback) { + DumpQuotaTableHelper* helper = new DumpQuotaTableHelper; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread, + base::Unretained(helper)), + base::Bind(&DumpQuotaTableHelper::DidDumpQuotaTable, + base::Owned(helper), + weak_factory_.GetWeakPtr(), + callback)); +} + +void QuotaManager::DumpOriginInfoTable( + const DumpOriginInfoTableCallback& callback) { + DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread, + base::Unretained(helper)), + base::Bind(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable, + base::Owned(helper), + weak_factory_.GetWeakPtr(), + callback)); +} + +void QuotaManager::StartEviction() { + DCHECK(!temporary_storage_evictor_.get()); + temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor( + this, kEvictionIntervalInMilliSeconds)); + if (desired_available_space_ >= 0) + temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction( + desired_available_space_); + temporary_storage_evictor_->Start(); +} + +void QuotaManager::DeleteOriginFromDatabase( + const GURL& origin, StorageType type) { + LazyInitialize(); + if (db_disabled_) + return; + + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&DeleteOriginInfoOnDBThread, origin, type), + base::Bind(&QuotaManager::DidDatabaseWork, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidOriginDataEvicted(QuotaStatusCode status) { + DCHECK(io_thread_->BelongsToCurrentThread()); + + // We only try evict origins that are not in use, so basically + // deletion attempt for eviction should not fail. Let's record + // the origin if we get error and exclude it from future eviction + // if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted). + if (status != kQuotaStatusOk) + origins_in_error_[eviction_context_.evicted_origin]++; + + eviction_context_.evict_origin_data_callback.Run(status); + eviction_context_.evict_origin_data_callback.Reset(); +} + +void QuotaManager::ReportHistogram() { + GetGlobalUsage(kStorageTypeTemporary, + base::Bind( + &QuotaManager::DidGetTemporaryGlobalUsageForHistogram, + weak_factory_.GetWeakPtr())); + GetGlobalUsage(kStorageTypePersistent, + base::Bind( + &QuotaManager::DidGetPersistentGlobalUsageForHistogram, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidGetTemporaryGlobalUsageForHistogram( + int64 usage, + int64 unlimited_usage) { + UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage); + + std::set<GURL> origins; + GetCachedOrigins(kStorageTypeTemporary, &origins); + + size_t num_origins = origins.size(); + size_t protected_origins = 0; + size_t unlimited_origins = 0; + CountOriginType(origins, special_storage_policy_, + &protected_origins, &unlimited_origins); + + UMA_HISTOGRAM_COUNTS("Quota.NumberOfTemporaryStorageOrigins", + num_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedTemporaryStorageOrigins", + protected_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedTemporaryStorageOrigins", + unlimited_origins); +} + +void QuotaManager::DidGetPersistentGlobalUsageForHistogram( + int64 usage, + int64 unlimited_usage) { + UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage); + + std::set<GURL> origins; + GetCachedOrigins(kStorageTypePersistent, &origins); + + size_t num_origins = origins.size(); + size_t protected_origins = 0; + size_t unlimited_origins = 0; + CountOriginType(origins, special_storage_policy_, + &protected_origins, &unlimited_origins); + + UMA_HISTOGRAM_COUNTS("Quota.NumberOfPersistentStorageOrigins", + num_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedPersistentStorageOrigins", + protected_origins); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedPersistentStorageOrigins", + unlimited_origins); +} + +void QuotaManager::GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) { + LazyInitialize(); + // This must not be called while there's an in-flight task. + DCHECK(lru_origin_callback_.is_null()); + lru_origin_callback_ = callback; + if (db_disabled_) { + lru_origin_callback_.Run(GURL()); + lru_origin_callback_.Reset(); + return; + } + + std::set<GURL>* exceptions = new std::set<GURL>; + for (std::map<GURL, int>::const_iterator p = origins_in_use_.begin(); + p != origins_in_use_.end(); + ++p) { + if (p->second > 0) + exceptions->insert(p->first); + } + for (std::map<GURL, int>::const_iterator p = origins_in_error_.begin(); + p != origins_in_error_.end(); + ++p) { + if (p->second > QuotaManager::kThresholdOfErrorsToBeBlacklisted) + exceptions->insert(p->first); + } + + GURL* url = new GURL; + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&GetLRUOriginOnDBThread, + type, + base::Owned(exceptions), + special_storage_policy_, + base::Unretained(url)), + base::Bind(&QuotaManager::DidGetLRUOrigin, + weak_factory_.GetWeakPtr(), + base::Owned(url))); +} + +void QuotaManager::EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) { + DCHECK(io_thread_->BelongsToCurrentThread()); + DCHECK_EQ(type, kStorageTypeTemporary); + + eviction_context_.evicted_origin = origin; + eviction_context_.evicted_type = type; + eviction_context_.evict_origin_data_callback = callback; + + DeleteOriginData(origin, type, QuotaClient::kAllClientsMask, + base::Bind(&QuotaManager::DidOriginDataEvicted, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) { + DCHECK(io_thread_->BelongsToCurrentThread()); + LazyInitialize(); + + UsageAndQuotaCallbackDispatcher* dispatcher = + new UsageAndQuotaCallbackDispatcher(this); + GetUsageTracker(kStorageTypeTemporary)-> + GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback()); + GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback()); + GetAvailableSpace(dispatcher->GetAvailableSpaceCallback()); + dispatcher->WaitForResults(callback); +} + +void QuotaManager::DidSetTemporaryGlobalOverrideQuota( + const QuotaCallback& callback, + const int64* new_quota, + bool success) { + QuotaStatusCode status = kQuotaErrorInvalidAccess; + DidDatabaseWork(success); + if (success) { + temporary_quota_override_ = *new_quota; + status = kQuotaStatusOk; + } + + if (callback.is_null()) + return; + + callback.Run(status, *new_quota); +} + +void QuotaManager::DidGetPersistentHostQuota(const std::string& host, + const int64* quota, + bool success) { + DidDatabaseWork(success); + persistent_host_quota_callbacks_.Run( + host, MakeTuple(kQuotaStatusOk, *quota)); +} + +void QuotaManager::DidSetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback, + const int64* new_quota, + bool success) { + DidDatabaseWork(success); + callback.Run(success ? kQuotaStatusOk : kQuotaErrorInvalidAccess, *new_quota); +} + +void QuotaManager::DidInitialize(int64* temporary_quota_override, + int64* desired_available_space, + bool success) { + temporary_quota_override_ = *temporary_quota_override; + desired_available_space_ = *desired_available_space; + temporary_quota_initialized_ = true; + DidDatabaseWork(success); + + histogram_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds( + kReportHistogramInterval), + this, &QuotaManager::ReportHistogram); + + db_initialization_callbacks_.Run(MakeTuple()); + GetTemporaryGlobalQuota( + base::Bind(&QuotaManager::DidGetInitialTemporaryGlobalQuota, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidGetLRUOrigin(const GURL* origin, + bool success) { + DidDatabaseWork(success); + // Make sure the returned origin is (still) not in the origin_in_use_ set + // and has not been accessed since we posted the task. + if (origins_in_use_.find(*origin) != origins_in_use_.end() || + access_notified_origins_.find(*origin) != access_notified_origins_.end()) + lru_origin_callback_.Run(GURL()); + else + lru_origin_callback_.Run(*origin); + access_notified_origins_.clear(); + lru_origin_callback_.Reset(); +} + +void QuotaManager::DidGetInitialTemporaryGlobalQuota( + QuotaStatusCode status, int64 quota_unused) { + if (eviction_disabled_) + return; + + std::set<GURL>* origins = new std::set<GURL>; + temporary_usage_tracker_->GetCachedOrigins(origins); + // This will call the StartEviction() when initial origin registration + // is completed. + PostTaskAndReplyWithResultForDBThread( + FROM_HERE, + base::Bind(&InitializeTemporaryOriginsInfoOnDBThread, + base::Owned(origins)), + base::Bind(&QuotaManager::DidInitializeTemporaryOriginsInfo, + weak_factory_.GetWeakPtr())); +} + +void QuotaManager::DidInitializeTemporaryOriginsInfo(bool success) { + DidDatabaseWork(success); + if (success) + StartEviction(); +} + +void QuotaManager::DidGetAvailableSpace(int64 space) { + available_space_callbacks_.Run(MakeTuple(kQuotaStatusOk, space)); +} + +void QuotaManager::DidDatabaseWork(bool success) { + db_disabled_ = !success; +} + +void QuotaManager::DeleteOnCorrectThread() const { + if (!io_thread_->BelongsToCurrentThread() && + io_thread_->DeleteSoon(FROM_HERE, this)) { + return; + } + delete this; +} + +void QuotaManager::PostTaskAndReplyWithResultForDBThread( + const tracked_objects::Location& from_here, + const base::Callback<bool(QuotaDatabase*)>& task, + const base::Callback<void(bool)>& reply) { + // Deleting manager will post another task to DB thread to delete + // |database_|, therefore we can be sure that database_ is alive when this + // task runs. + base::PostTaskAndReplyWithResult( + db_thread_, + from_here, + base::Bind(task, base::Unretained(database_.get())), + reply); +} + +// QuotaManagerProxy ---------------------------------------------------------- + +void QuotaManagerProxy::RegisterClient(QuotaClient* client) { + if (!io_thread_->BelongsToCurrentThread() && + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::RegisterClient, this, client))) { + return; + } + + if (manager_) + manager_->RegisterClient(client); + else + client->OnQuotaManagerDestroyed(); +} + +void QuotaManagerProxy::NotifyStorageAccessed( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyStorageAccessed, this, client_id, + origin, type)); + return; + } + + if (manager_) + manager_->NotifyStorageAccessed(client_id, origin, type); +} + +void QuotaManagerProxy::NotifyStorageModified( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyStorageModified, this, client_id, + origin, type, delta)); + return; + } + + if (manager_) + manager_->NotifyStorageModified(client_id, origin, type, delta); +} + +void QuotaManagerProxy::NotifyOriginInUse( + const GURL& origin) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyOriginInUse, this, origin)); + return; + } + + if (manager_) + manager_->NotifyOriginInUse(origin); +} + +void QuotaManagerProxy::NotifyOriginNoLongerInUse( + const GURL& origin) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::NotifyOriginNoLongerInUse, this, + origin)); + return; + } + if (manager_) + manager_->NotifyOriginNoLongerInUse(origin); +} + +void QuotaManagerProxy::SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled) { + if (!io_thread_->BelongsToCurrentThread()) { + io_thread_->PostTask( + FROM_HERE, + base::Bind(&QuotaManagerProxy::SetUsageCacheEnabled, this, + client_id, origin, type, enabled)); + return; + } + if (manager_) + manager_->SetUsageCacheEnabled(client_id, origin, type, enabled); +} + +QuotaManager* QuotaManagerProxy::quota_manager() const { + DCHECK(!io_thread_ || io_thread_->BelongsToCurrentThread()); + return manager_; +} + +QuotaManagerProxy::QuotaManagerProxy( + QuotaManager* manager, base::SingleThreadTaskRunner* io_thread) + : manager_(manager), io_thread_(io_thread) { +} + +QuotaManagerProxy::~QuotaManagerProxy() { +} + +} // namespace quota diff --git a/webkit/browser/quota/quota_manager.h b/webkit/browser/quota/quota_manager.h new file mode 100644 index 0000000..9496b97 --- /dev/null +++ b/webkit/browser/quota/quota_manager.h @@ -0,0 +1,471 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_QUOTA_MANAGER_H_ +#define WEBKIT_BROWSER_QUOTA_QUOTA_MANAGER_H_ + +#include <deque> +#include <list> +#include <map> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/sequenced_task_runner_helpers.h" +#include "webkit/browser/quota/quota_callbacks.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_database.h" +#include "webkit/browser/quota/quota_task.h" +#include "webkit/browser/quota/special_storage_policy.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class FilePath; +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace quota_internals { +class QuotaInternalsProxy; +} + +namespace quota { + +class MockQuotaManager; +class QuotaDatabase; +class QuotaManagerProxy; +class QuotaTemporaryStorageEvictor; +class UsageTracker; + +struct QuotaManagerDeleter; + +struct WEBKIT_STORAGE_EXPORT UsageAndQuota { + int64 usage; + int64 global_limited_usage; + int64 quota; + int64 available_disk_space; + + UsageAndQuota(); + UsageAndQuota(int64 usage, + int64 global_limited_usage, + int64 quota, + int64 available_disk_space); +}; + +// An interface called by QuotaTemporaryStorageEvictor. +class WEBKIT_STORAGE_EXPORT QuotaEvictionHandler { + public: + typedef base::Callback<void(const GURL&)> GetLRUOriginCallback; + typedef StatusCallback EvictOriginDataCallback; + typedef base::Callback<void(QuotaStatusCode status, + const UsageAndQuota& usage_and_quota)> + UsageAndQuotaCallback; + + // Returns the least recently used origin. It might return empty + // GURL when there are no evictable origins. + virtual void GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) = 0; + + virtual void EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) = 0; + + virtual void GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) = 0; + + protected: + virtual ~QuotaEvictionHandler() {} +}; + +struct UsageInfo { + UsageInfo(const std::string& host, StorageType type, int64 usage) + : host(host), + type(type), + usage(usage) {} + std::string host; + StorageType type; + int64 usage; +}; + +// The quota manager class. This class is instantiated per profile and +// held by the profile. With the exception of the constructor and the +// proxy() method, all methods should only be called on the IO thread. +class WEBKIT_STORAGE_EXPORT QuotaManager + : public QuotaTaskObserver, + public QuotaEvictionHandler, + public base::RefCountedThreadSafe<QuotaManager, QuotaManagerDeleter> { + public: + typedef base::Callback<void(QuotaStatusCode, + int64 /* usage */, + int64 /* quota */)> + GetUsageAndQuotaCallback; + + static const int64 kIncognitoDefaultQuotaLimit; + static const int64 kNoLimit; + + QuotaManager(bool is_incognito, + const base::FilePath& profile_path, + base::SingleThreadTaskRunner* io_thread, + base::SequencedTaskRunner* db_thread, + SpecialStoragePolicy* special_storage_policy); + + // Returns a proxy object that can be used on any thread. + QuotaManagerProxy* proxy() { return proxy_.get(); } + + // Called by clients or webapps. Returns usage per host. + void GetUsageInfo(const GetUsageInfoCallback& callback); + + // Called by Web Apps. + // This method is declared as virtual to allow test code to override it. + virtual void GetUsageAndQuotaForWebApps( + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback); + + // Called by StorageClients. + // This method is declared as virtual to allow test code to override it. + // + // For UnlimitedStorage origins, this version skips usage and quota handling + // to avoid extra query cost. + // Do not call this method for apps/user-facing code. + virtual void GetUsageAndQuota( + const GURL& origin, + StorageType type, + const GetUsageAndQuotaCallback& callback); + + // Called by clients via proxy. + // Client storage should call this method when storage is accessed. + // Used to maintain LRU ordering. + void NotifyStorageAccessed(QuotaClient::ID client_id, + const GURL& origin, + StorageType type); + + // Called by clients via proxy. + // Client storage must call this method whenever they have made any + // modifications that change the amount of data stored in their storage. + void NotifyStorageModified(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta); + + // Used to avoid evicting origins with open pages. + // A call to NotifyOriginInUse must be balanced by a later call + // to NotifyOriginNoLongerInUse. + void NotifyOriginInUse(const GURL& origin); + void NotifyOriginNoLongerInUse(const GURL& origin); + bool IsOriginInUse(const GURL& origin) const { + return origins_in_use_.find(origin) != origins_in_use_.end(); + } + + void SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled); + + // DeleteOriginData and DeleteHostData (surprisingly enough) delete data of a + // particular StorageType associated with either a specific origin or set of + // origins. Each method additionally requires a |quota_client_mask| which + // specifies the types of QuotaClients to delete from the origin. This is + // specified by the caller as a bitmask built from QuotaClient::IDs. Setting + // the mask to QuotaClient::kAllClientsMask will remove all clients from the + // origin, regardless of type. + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + int quota_client_mask, + const StatusCallback& callback); + void DeleteHostData(const std::string& host, + StorageType type, + int quota_client_mask, + const StatusCallback& callback); + + // Called by UI and internal modules. + void GetAvailableSpace(const AvailableSpaceCallback& callback); + void GetTemporaryGlobalQuota(const QuotaCallback& callback); + + // Ok to call with NULL callback. + void SetTemporaryGlobalOverrideQuota(int64 new_quota, + const QuotaCallback& callback); + + void GetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback); + void SetPersistentHostQuota(const std::string& host, + int64 new_quota, + const QuotaCallback& callback); + void GetGlobalUsage(StorageType type, const GlobalUsageCallback& callback); + void GetHostUsage(const std::string& host, StorageType type, + const UsageCallback& callback); + + void GetStatistics(std::map<std::string, std::string>* statistics); + + bool IsStorageUnlimited(const GURL& origin, StorageType type) const; + + bool CanQueryDiskSize(const GURL& origin) const { + return special_storage_policy_.get() && + special_storage_policy_->CanQueryDiskSize(origin); + } + + virtual void GetOriginsModifiedSince(StorageType type, + base::Time modified_since, + const GetOriginsCallback& callback); + + bool ResetUsageTracker(StorageType type); + + // Determines the portion of the temp pool that can be + // utilized by a single host (ie. 5 for 20%). + static const int kPerHostTemporaryPortion; + + static const char kDatabaseName[]; + + static const int64 kMinimumPreserveForSystem; + + static const int kThresholdOfErrorsToBeBlacklisted; + + static const int kEvictionIntervalInMilliSeconds; + + // This is kept non-const so that test code can change the value. + // TODO(kinuko): Make this a real const value and add a proper way to set + // the quota for syncable storage. (http://crbug.com/155488) + static int64 kSyncableStorageDefaultHostQuota; + + protected: + virtual ~QuotaManager(); + + private: + friend class base::DeleteHelper<QuotaManager>; + friend class base::RefCountedThreadSafe<QuotaManager, QuotaManagerDeleter>; + friend class MockQuotaManager; + friend class MockStorageClient; + friend class quota_internals::QuotaInternalsProxy; + friend class QuotaManagerProxy; + friend class QuotaManagerTest; + friend class QuotaTemporaryStorageEvictor; + friend struct QuotaManagerDeleter; + + class GetUsageInfoTask; + + class OriginDataDeleter; + class HostDataDeleter; + + class GetModifiedSinceHelper; + class DumpQuotaTableHelper; + class DumpOriginInfoTableHelper; + + typedef QuotaDatabase::QuotaTableEntry QuotaTableEntry; + typedef QuotaDatabase::OriginInfoTableEntry OriginInfoTableEntry; + typedef std::vector<QuotaTableEntry> QuotaTableEntries; + typedef std::vector<OriginInfoTableEntry> OriginInfoTableEntries; + + // Function pointer type used to store the function which returns the + // available disk space for the disk containing the given FilePath. + typedef int64 (*GetAvailableDiskSpaceFn)(const base::FilePath&); + + typedef base::Callback<void(const QuotaTableEntries&)> + DumpQuotaTableCallback; + typedef base::Callback<void(const OriginInfoTableEntries&)> + DumpOriginInfoTableCallback; + + struct EvictionContext { + EvictionContext(); + virtual ~EvictionContext(); + GURL evicted_origin; + StorageType evicted_type; + + EvictOriginDataCallback evict_origin_data_callback; + }; + + typedef QuotaEvictionHandler::UsageAndQuotaCallback + UsageAndQuotaDispatcherCallback; + + // This initialization method is lazily called on the IO thread + // when the first quota manager API is called. + // Initialize must be called after all quota clients are added to the + // manager by RegisterStorage. + void LazyInitialize(); + + // Called by clients via proxy. + // Registers a quota client to the manager. + // The client must remain valid until OnQuotaManagerDestored is called. + void RegisterClient(QuotaClient* client); + + UsageTracker* GetUsageTracker(StorageType type) const; + + // Extract cached origins list from the usage tracker. + // (Might return empty list if no origin is tracked by the tracker.) + void GetCachedOrigins(StorageType type, std::set<GURL>* origins); + + // These internal methods are separately defined mainly for testing. + void NotifyStorageAccessedInternal( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + base::Time accessed_time); + void NotifyStorageModifiedInternal( + QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta, + base::Time modified_time); + + void DumpQuotaTable(const DumpQuotaTableCallback& callback); + void DumpOriginInfoTable(const DumpOriginInfoTableCallback& callback); + + // Methods for eviction logic. + void StartEviction(); + void DeleteOriginFromDatabase(const GURL& origin, StorageType type); + + void DidOriginDataEvicted(QuotaStatusCode status); + + void ReportHistogram(); + void DidGetTemporaryGlobalUsageForHistogram(int64 usage, + int64 unlimited_usage); + void DidGetPersistentGlobalUsageForHistogram(int64 usage, + int64 unlimited_usage); + + // QuotaEvictionHandler. + virtual void GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) OVERRIDE; + virtual void EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) OVERRIDE; + virtual void GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) OVERRIDE; + + void DidSetTemporaryGlobalOverrideQuota(const QuotaCallback& callback, + const int64* new_quota, + bool success); + void DidGetPersistentHostQuota(const std::string& host, + const int64* quota, + bool success); + void DidSetPersistentHostQuota(const std::string& host, + const QuotaCallback& callback, + const int64* new_quota, + bool success); + void DidInitialize(int64* temporary_quota_override, + int64* desired_available_space, + bool success); + void DidGetLRUOrigin(const GURL* origin, + bool success); + void DidGetInitialTemporaryGlobalQuota(QuotaStatusCode status, + int64 quota_unused); + void DidInitializeTemporaryOriginsInfo(bool success); + void DidGetAvailableSpace(int64 space); + void DidDatabaseWork(bool success); + + void DeleteOnCorrectThread() const; + + void PostTaskAndReplyWithResultForDBThread( + const tracked_objects::Location& from_here, + const base::Callback<bool(QuotaDatabase*)>& task, + const base::Callback<void(bool)>& reply); + + const bool is_incognito_; + const base::FilePath profile_path_; + + scoped_refptr<QuotaManagerProxy> proxy_; + bool db_disabled_; + bool eviction_disabled_; + scoped_refptr<base::SingleThreadTaskRunner> io_thread_; + scoped_refptr<base::SequencedTaskRunner> db_thread_; + mutable scoped_ptr<QuotaDatabase> database_; + + GetLRUOriginCallback lru_origin_callback_; + std::set<GURL> access_notified_origins_; + + QuotaClientList clients_; + + scoped_ptr<UsageTracker> temporary_usage_tracker_; + scoped_ptr<UsageTracker> persistent_usage_tracker_; + scoped_ptr<UsageTracker> syncable_usage_tracker_; + // TODO(michaeln): Need a way to clear the cache, drop and + // reinstantiate the trackers when they're not handling requests. + + scoped_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_; + EvictionContext eviction_context_; + + ClosureQueue db_initialization_callbacks_; + AvailableSpaceCallbackQueue available_space_callbacks_; + GlobalQuotaCallbackQueue temporary_global_quota_callbacks_; + HostQuotaCallbackMap persistent_host_quota_callbacks_; + + bool temporary_quota_initialized_; + int64 temporary_quota_override_; + + int64 desired_available_space_; + + // Map from origin to count. + std::map<GURL, int> origins_in_use_; + // Map from origin to error count. + std::map<GURL, int> origins_in_error_; + + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; + + base::WeakPtrFactory<QuotaManager> weak_factory_; + base::RepeatingTimer<QuotaManager> histogram_timer_; + + // Pointer to the function used to get the available disk space. This is + // overwritten by QuotaManagerTest in order to attain a deterministic reported + // value. The default value points to base::SysInfo::AmountOfFreeDiskSpace. + GetAvailableDiskSpaceFn get_disk_space_fn_; + + DISALLOW_COPY_AND_ASSIGN(QuotaManager); +}; + +struct QuotaManagerDeleter { + static void Destruct(const QuotaManager* manager) { + manager->DeleteOnCorrectThread(); + } +}; + +// The proxy may be called and finally released on any thread. +class WEBKIT_STORAGE_EXPORT QuotaManagerProxy + : public base::RefCountedThreadSafe<QuotaManagerProxy> { + public: + virtual void RegisterClient(QuotaClient* client); + virtual void NotifyStorageAccessed(QuotaClient::ID client_id, + const GURL& origin, + StorageType type); + virtual void NotifyStorageModified(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + int64 delta); + virtual void NotifyOriginInUse(const GURL& origin); + virtual void NotifyOriginNoLongerInUse(const GURL& origin); + + virtual void SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + StorageType type, + bool enabled); + + // This method may only be called on the IO thread. + // It may return NULL if the manager has already been deleted. + QuotaManager* quota_manager() const; + + protected: + friend class QuotaManager; + friend class base::RefCountedThreadSafe<QuotaManagerProxy>; + + QuotaManagerProxy(QuotaManager* manager, + base::SingleThreadTaskRunner* io_thread); + virtual ~QuotaManagerProxy(); + + QuotaManager* manager_; // only accessed on the io thread + scoped_refptr<base::SingleThreadTaskRunner> io_thread_; + + DISALLOW_COPY_AND_ASSIGN(QuotaManagerProxy); +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_QUOTA_MANAGER_H_ diff --git a/webkit/browser/quota/quota_manager_unittest.cc b/webkit/browser/quota/quota_manager_unittest.cc new file mode 100644 index 0000000..4d902d0 --- /dev/null +++ b/webkit/browser/quota/quota_manager_unittest.cc @@ -0,0 +1,2165 @@ +// Copyright 2013 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 <set> +#include <sstream> +#include <vector> + +#include "base/bind.h" +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/stl_util.h" +#include "base/sys_info.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/mock_storage_client.h" +#include "webkit/browser/quota/quota_database.h" +#include "webkit/browser/quota/quota_manager.h" + +using base::MessageLoopProxy; + +namespace quota { + +namespace { + +// For shorter names. +const StorageType kTemp = kStorageTypeTemporary; +const StorageType kPerm = kStorageTypePersistent; +const StorageType kSync = kStorageTypeSyncable; + +const int kAllClients = QuotaClient::kAllClientsMask; + +const int64 kAvailableSpaceForApp = 13377331U; + +const int64 kMinimumPreserveForSystem = QuotaManager::kMinimumPreserveForSystem; +const int kPerHostTemporaryPortion = QuotaManager::kPerHostTemporaryPortion; + +// Returns a deterministic value for the amount of available disk space. +int64 GetAvailableDiskSpaceForTest(const base::FilePath&) { + return kAvailableSpaceForApp + kMinimumPreserveForSystem; +} + +} // namespace + +class QuotaManagerTest : public testing::Test { + protected: + typedef QuotaManager::QuotaTableEntry QuotaTableEntry; + typedef QuotaManager::QuotaTableEntries QuotaTableEntries; + typedef QuotaManager::OriginInfoTableEntries OriginInfoTableEntries; + + public: + QuotaManagerTest() + : weak_factory_(this), + mock_time_counter_(0) { + } + + virtual void SetUp() { + ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); + mock_special_storage_policy_ = new MockSpecialStoragePolicy; + ResetQuotaManager(false /* is_incognito */); + } + + virtual void TearDown() { + // Make sure the quota manager cleans up correctly. + quota_manager_ = NULL; + base::MessageLoop::current()->RunUntilIdle(); + } + + protected: + void ResetQuotaManager(bool is_incognito) { + quota_manager_ = new QuotaManager( + is_incognito, + data_dir_.path(), + MessageLoopProxy::current(), + MessageLoopProxy::current(), + mock_special_storage_policy_); + // Don't (automatically) start the eviction for testing. + quota_manager_->eviction_disabled_ = true; + // Don't query the hard disk for remaining capacity. + quota_manager_->get_disk_space_fn_ = &GetAvailableDiskSpaceForTest; + additional_callback_count_ = 0; + } + + MockStorageClient* CreateClient( + const MockOriginData* mock_data, + size_t mock_data_size, + QuotaClient::ID id) { + return new MockStorageClient(quota_manager_->proxy(), + mock_data, id, mock_data_size); + } + + void RegisterClient(MockStorageClient* client) { + quota_manager_->proxy()->RegisterClient(client); + } + + void GetUsageInfo() { + usage_info_.clear(); + quota_manager_->GetUsageInfo( + base::Bind(&QuotaManagerTest::DidGetUsageInfo, + weak_factory_.GetWeakPtr())); + } + + void GetUsageAndQuotaForWebApps(const GURL& origin, + StorageType type) { + quota_status_ = kQuotaStatusUnknown; + usage_ = -1; + quota_ = -1; + quota_manager_->GetUsageAndQuotaForWebApps( + origin, type, base::Bind(&QuotaManagerTest::DidGetUsageAndQuota, + weak_factory_.GetWeakPtr())); + } + + void GetUsageAndQuotaForStorageClient(const GURL& origin, + StorageType type) { + quota_status_ = kQuotaStatusUnknown; + usage_ = -1; + quota_ = -1; + quota_manager_->GetUsageAndQuota( + origin, type, base::Bind(&QuotaManagerTest::DidGetUsageAndQuota, + weak_factory_.GetWeakPtr())); + } + + void GetTemporaryGlobalQuota() { + quota_status_ = kQuotaStatusUnknown; + quota_ = -1; + quota_manager_->GetTemporaryGlobalQuota( + base::Bind(&QuotaManagerTest::DidGetQuota, + weak_factory_.GetWeakPtr())); + } + + void SetTemporaryGlobalQuota(int64 new_quota) { + quota_status_ = kQuotaStatusUnknown; + quota_ = -1; + quota_manager_->SetTemporaryGlobalOverrideQuota( + new_quota, + base::Bind(&QuotaManagerTest::DidGetQuota, + weak_factory_.GetWeakPtr())); + } + + void GetPersistentHostQuota(const std::string& host) { + quota_status_ = kQuotaStatusUnknown; + quota_ = -1; + quota_manager_->GetPersistentHostQuota( + host, + base::Bind(&QuotaManagerTest::DidGetHostQuota, + weak_factory_.GetWeakPtr())); + } + + void SetPersistentHostQuota(const std::string& host, int64 new_quota) { + quota_status_ = kQuotaStatusUnknown; + quota_ = -1; + quota_manager_->SetPersistentHostQuota( + host, new_quota, + base::Bind(&QuotaManagerTest::DidGetHostQuota, + weak_factory_.GetWeakPtr())); + } + + void GetGlobalUsage(StorageType type) { + usage_ = -1; + unlimited_usage_ = -1; + quota_manager_->GetGlobalUsage( + type, + base::Bind(&QuotaManagerTest::DidGetGlobalUsage, + weak_factory_.GetWeakPtr())); + } + + void GetHostUsage(const std::string& host, StorageType type) { + usage_ = -1; + quota_manager_->GetHostUsage( + host, type, + base::Bind(&QuotaManagerTest::DidGetHostUsage, + weak_factory_.GetWeakPtr())); + } + + void RunAdditionalUsageAndQuotaTask(const GURL& origin, StorageType type) { + quota_manager_->GetUsageAndQuota( + origin, type, + base::Bind(&QuotaManagerTest::DidGetUsageAndQuotaAdditional, + weak_factory_.GetWeakPtr())); + } + + void DeleteClientOriginData(QuotaClient* client, + const GURL& origin, + StorageType type) { + DCHECK(client); + quota_status_ = kQuotaStatusUnknown; + client->DeleteOriginData( + origin, type, + base::Bind(&QuotaManagerTest::StatusCallback, + weak_factory_.GetWeakPtr())); + } + + void EvictOriginData(const GURL& origin, + StorageType type) { + quota_status_ = kQuotaStatusUnknown; + quota_manager_->EvictOriginData( + origin, type, + base::Bind(&QuotaManagerTest::StatusCallback, + weak_factory_.GetWeakPtr())); + } + + void DeleteOriginData(const GURL& origin, + StorageType type, + int quota_client_mask) { + quota_status_ = kQuotaStatusUnknown; + quota_manager_->DeleteOriginData( + origin, type, quota_client_mask, + base::Bind(&QuotaManagerTest::StatusCallback, + weak_factory_.GetWeakPtr())); + } + + void DeleteHostData(const std::string& host, + StorageType type, + int quota_client_mask) { + quota_status_ = kQuotaStatusUnknown; + quota_manager_->DeleteHostData( + host, type, quota_client_mask, + base::Bind(&QuotaManagerTest::StatusCallback, + weak_factory_.GetWeakPtr())); + } + + void GetAvailableSpace() { + quota_status_ = kQuotaStatusUnknown; + available_space_ = -1; + quota_manager_->GetAvailableSpace( + base::Bind(&QuotaManagerTest::DidGetAvailableSpace, + weak_factory_.GetWeakPtr())); + } + + void GetUsageAndQuotaForEviction() { + quota_status_ = kQuotaStatusUnknown; + usage_ = -1; + unlimited_usage_ = -1; + quota_ = -1; + available_space_ = -1; + quota_manager_->GetUsageAndQuotaForEviction( + base::Bind(&QuotaManagerTest::DidGetUsageAndQuotaForEviction, + weak_factory_.GetWeakPtr())); + } + + void GetCachedOrigins(StorageType type, std::set<GURL>* origins) { + ASSERT_TRUE(origins != NULL); + origins->clear(); + quota_manager_->GetCachedOrigins(type, origins); + } + + void NotifyStorageAccessed(QuotaClient* client, + const GURL& origin, + StorageType type) { + DCHECK(client); + quota_manager_->NotifyStorageAccessedInternal( + client->id(), origin, type, IncrementMockTime()); + } + + void DeleteOriginFromDatabase(const GURL& origin, StorageType type) { + quota_manager_->DeleteOriginFromDatabase(origin, type); + } + + void GetLRUOrigin(StorageType type) { + lru_origin_ = GURL(); + quota_manager_->GetLRUOrigin( + type, + base::Bind(&QuotaManagerTest::DidGetLRUOrigin, + weak_factory_.GetWeakPtr())); + } + + void NotifyOriginInUse(const GURL& origin) { + quota_manager_->NotifyOriginInUse(origin); + } + + void NotifyOriginNoLongerInUse(const GURL& origin) { + quota_manager_->NotifyOriginNoLongerInUse(origin); + } + + void GetOriginsModifiedSince(StorageType type, base::Time modified_since) { + modified_origins_.clear(); + modified_origins_type_ = kStorageTypeUnknown; + quota_manager_->GetOriginsModifiedSince( + type, modified_since, + base::Bind(&QuotaManagerTest::DidGetModifiedOrigins, + weak_factory_.GetWeakPtr())); + } + + void DumpQuotaTable() { + quota_entries_.clear(); + quota_manager_->DumpQuotaTable( + base::Bind(&QuotaManagerTest::DidDumpQuotaTable, + weak_factory_.GetWeakPtr())); + } + + void DumpOriginInfoTable() { + origin_info_entries_.clear(); + quota_manager_->DumpOriginInfoTable( + base::Bind(&QuotaManagerTest::DidDumpOriginInfoTable, + weak_factory_.GetWeakPtr())); + } + + void DidGetUsageInfo(const UsageInfoEntries& entries) { + usage_info_.insert(usage_info_.begin(), entries.begin(), entries.end()); + } + + void DidGetUsageAndQuota(QuotaStatusCode status, int64 usage, int64 quota) { + quota_status_ = status; + usage_ = usage; + quota_ = quota; + } + + void DidGetQuota(QuotaStatusCode status, + int64 quota) { + quota_status_ = status; + quota_ = quota; + } + + void DidGetAvailableSpace(QuotaStatusCode status, int64 available_space) { + quota_status_ = status; + available_space_ = available_space; + } + + void DidGetHostQuota(QuotaStatusCode status, + int64 quota) { + quota_status_ = status; + quota_ = quota; + } + + void DidGetGlobalUsage(int64 usage, + int64 unlimited_usage) { + usage_ = usage; + unlimited_usage_ = unlimited_usage; + } + + void DidGetHostUsage(int64 usage) { + usage_ = usage; + } + + void StatusCallback(QuotaStatusCode status) { + ++status_callback_count_; + quota_status_ = status; + } + + void DidGetUsageAndQuotaForEviction(QuotaStatusCode status, + const UsageAndQuota& usage_and_quota) { + quota_status_ = status; + limited_usage_ = usage_and_quota.global_limited_usage; + quota_ = usage_and_quota.quota; + available_space_ = usage_and_quota.available_disk_space; + } + + void DidGetLRUOrigin(const GURL& origin) { + lru_origin_ = origin; + } + + void DidGetModifiedOrigins(const std::set<GURL>& origins, StorageType type) { + modified_origins_ = origins; + modified_origins_type_ = type; + } + + void DidDumpQuotaTable(const QuotaTableEntries& entries) { + quota_entries_ = entries; + } + + void DidDumpOriginInfoTable(const OriginInfoTableEntries& entries) { + origin_info_entries_ = entries; + } + + void GetUsage_WithModifyTestBody(const StorageType type); + + void set_additional_callback_count(int c) { additional_callback_count_ = c; } + int additional_callback_count() const { return additional_callback_count_; } + void DidGetUsageAndQuotaAdditional( + QuotaStatusCode status, int64 usage, int64 quota) { + ++additional_callback_count_; + } + + QuotaManager* quota_manager() const { return quota_manager_.get(); } + void set_quota_manager(QuotaManager* quota_manager) { + quota_manager_ = quota_manager; + } + + MockSpecialStoragePolicy* mock_special_storage_policy() const { + return mock_special_storage_policy_.get(); + } + + QuotaStatusCode status() const { return quota_status_; } + const UsageInfoEntries& usage_info() const { return usage_info_; } + int64 usage() const { return usage_; } + int64 limited_usage() const { return limited_usage_; } + int64 unlimited_usage() const { return unlimited_usage_; } + int64 quota() const { return quota_; } + int64 available_space() const { return available_space_; } + const GURL& lru_origin() const { return lru_origin_; } + const std::set<GURL>& modified_origins() const { return modified_origins_; } + StorageType modified_origins_type() const { return modified_origins_type_; } + const QuotaTableEntries& quota_entries() const { return quota_entries_; } + const OriginInfoTableEntries& origin_info_entries() const { + return origin_info_entries_; + } + base::FilePath profile_path() const { return data_dir_.path(); } + int status_callback_count() const { return status_callback_count_; } + void reset_status_callback_count() { status_callback_count_ = 0; } + + private: + base::Time IncrementMockTime() { + ++mock_time_counter_; + return base::Time::FromDoubleT(mock_time_counter_ * 10.0); + } + + base::MessageLoop message_loop_; + base::ScopedTempDir data_dir_; + base::WeakPtrFactory<QuotaManagerTest> weak_factory_; + + scoped_refptr<QuotaManager> quota_manager_; + scoped_refptr<MockSpecialStoragePolicy> mock_special_storage_policy_; + + QuotaStatusCode quota_status_; + UsageInfoEntries usage_info_; + int64 usage_; + int64 limited_usage_; + int64 unlimited_usage_; + int64 quota_; + int64 available_space_; + GURL lru_origin_; + std::set<GURL> modified_origins_; + StorageType modified_origins_type_; + QuotaTableEntries quota_entries_; + OriginInfoTableEntries origin_info_entries_; + int status_callback_count_; + + int additional_callback_count_; + + int mock_time_counter_; + + DISALLOW_COPY_AND_ASSIGN(QuotaManagerTest); +}; + +TEST_F(QuotaManagerTest, GetUsageInfo) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 10 }, + { "http://foo.com:8080/", kTemp, 15 }, + { "http://bar.com/", kTemp, 20 }, + { "http://bar.com/", kPerm, 50 }, + }; + static const MockOriginData kData2[] = { + { "https://foo.com/", kTemp, 30 }, + { "https://foo.com:8081/", kTemp, 35 }, + { "http://bar.com/", kPerm, 40 }, + { "http://example.com/", kPerm, 40 }, + }; + RegisterClient(CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem)); + RegisterClient(CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kDatabase)); + + GetUsageInfo(); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(4U, usage_info().size()); + for (size_t i = 0; i < usage_info().size(); ++i) { + const UsageInfo& info = usage_info()[i]; + if (info.host == "foo.com" && info.type == kTemp) { + EXPECT_EQ(10 + 15 + 30 + 35, info.usage); + } else if (info.host == "bar.com" && info.type == kTemp) { + EXPECT_EQ(20, info.usage); + } else if (info.host == "bar.com" && info.type == kPerm) { + EXPECT_EQ(50 + 40, info.usage); + } else if (info.host == "example.com" && info.type == kPerm) { + EXPECT_EQ(40, info.usage); + } else { + ADD_FAILURE() + << "Unexpected host, type: " << info.host << ", " << info.type; + } + } +} + +TEST_F(QuotaManagerTest, GetUsageAndQuota_Simple) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 10 }, + { "http://foo.com/", kPerm, 80 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(80, usage()); + EXPECT_EQ(0, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_LE(0, quota()); + int64 quota_returned_for_foo = quota(); + + GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(quota_returned_for_foo, quota()); +} + +TEST_F(QuotaManagerTest, GetUsage_NoClient) { + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); + + GetGlobalUsage(kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); +} + +TEST_F(QuotaManagerTest, GetUsage_EmptyClient) { + RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem)); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); + + GetGlobalUsage(kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + EXPECT_EQ(0, unlimited_usage()); +} + +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_MultiOrigins) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 10 }, + { "http://foo.com:8080/", kTemp, 20 }, + { "http://bar.com/", kTemp, 5 }, + { "https://bar.com/", kTemp, 7 }, + { "http://baz.com/", kTemp, 30 }, + { "http://foo.com/", kPerm, 40 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + + // This time explicitly sets a temporary global quota. + SetTemporaryGlobalQuota(100); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(100, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20, usage()); + + const int kPerHostQuota = 100 / kPerHostTemporaryPortion; + + // The host's quota should be its full portion of the global quota + // since global usage is under the global quota. + EXPECT_EQ(kPerHostQuota, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(5 + 7, usage()); + EXPECT_EQ(kPerHostQuota, quota()); +} + +TEST_F(QuotaManagerTest, GetUsage_MultipleClients) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://bar.com/", kTemp, 2 }, + { "http://bar.com/", kPerm, 4 }, + { "http://unlimited/", kPerm, 8 }, + { "http://installed/", kPerm, 16 }, + }; + static const MockOriginData kData2[] = { + { "https://foo.com/", kTemp, 128 }, + { "http://example.com/", kPerm, 256 }, + { "http://unlimited/", kTemp, 512 }, + { "http://installed/", kTemp, 1024 }, + }; + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); + mock_special_storage_policy()->GrantQueryDiskSize(GURL("http://installed/")); + RegisterClient(CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem)); + RegisterClient(CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kDatabase)); + + const int64 kTempQuotaBase = + GetAvailableDiskSpaceForTest(base::FilePath()) / kPerHostTemporaryPortion; + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1 + 128, usage()); + + GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4, usage()); + + GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(512, usage()); + EXPECT_EQ(std::min(kAvailableSpaceForApp, kTempQuotaBase) + usage(), quota()); + + GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(8, usage()); + EXPECT_EQ(kAvailableSpaceForApp + usage(), quota()); + + GetAvailableSpace(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_LE(0, available_space()); + + GetUsageAndQuotaForWebApps(GURL("http://installed/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1024, usage()); + EXPECT_EQ(std::min(kAvailableSpaceForApp, kTempQuotaBase) + usage(), quota()); + + GetUsageAndQuotaForWebApps(GURL("http://installed/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(16, usage()); + EXPECT_EQ(usage(), quota()); // Over-budget case. + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1 + 2 + 128 + 512 + 1024, usage()); + EXPECT_EQ(512, unlimited_usage()); + + GetGlobalUsage(kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4 + 8 + 16 + 256, usage()); + EXPECT_EQ(8, unlimited_usage()); +} + +void QuotaManagerTest::GetUsage_WithModifyTestBody(const StorageType type) { + const MockOriginData data[] = { + { "http://foo.com/", type, 10 }, + { "http://foo.com:1/", type, 20 }, + }; + MockStorageClient* client = CreateClient(data, ARRAYSIZE_UNSAFE(data), + QuotaClient::kFileSystem); + RegisterClient(client); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20, usage()); + + client->ModifyOriginAndNotify(GURL("http://foo.com/"), type, 30); + client->ModifyOriginAndNotify(GURL("http://foo.com:1/"), type, -5); + client->AddOriginAndNotify(GURL("https://foo.com/"), type, 1); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), type); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20 + 30 - 5 + 1, usage()); + int foo_usage = usage(); + + client->AddOriginAndNotify(GURL("http://bar.com/"), type, 40); + GetUsageAndQuotaForWebApps(GURL("http://bar.com/"), type); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(40, usage()); + + GetGlobalUsage(type); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(foo_usage + 40, usage()); + EXPECT_EQ(0, unlimited_usage()); +} + +TEST_F(QuotaManagerTest, GetTemporaryUsage_WithModify) { + GetUsage_WithModifyTestBody(kTemp); +} + +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_WithAdditionalTasks) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 10 }, + { "http://foo.com:8080/", kTemp, 20 }, + { "http://bar.com/", kTemp, 13 }, + { "http://foo.com/", kPerm, 40 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + SetTemporaryGlobalQuota(100); + base::MessageLoop::current()->RunUntilIdle(); + + const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20, usage()); + EXPECT_EQ(kPerHostQuota, quota()); + + set_additional_callback_count(0); + RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), + kTemp); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20, usage()); + EXPECT_EQ(kPerHostQuota, quota()); + EXPECT_EQ(2, additional_callback_count()); +} + +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_NukeManager) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 10 }, + { "http://foo.com:8080/", kTemp, 20 }, + { "http://bar.com/", kTemp, 13 }, + { "http://foo.com/", kPerm, 40 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + SetTemporaryGlobalQuota(100); + base::MessageLoop::current()->RunUntilIdle(); + + set_additional_callback_count(0); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), + kTemp); + RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), + kTemp); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients); + DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients); + + // Nuke before waiting for callbacks. + set_quota_manager(NULL); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaErrorAbort, status()); +} + +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Overbudget) { + static const MockOriginData kData[] = { + { "http://usage1/", kTemp, 1 }, + { "http://usage10/", kTemp, 10 }, + { "http://usage200/", kTemp, 200 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + SetTemporaryGlobalQuota(100); + base::MessageLoop::current()->RunUntilIdle(); + + const int kPerHostQuota = 100 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuotaForWebApps(GURL("http://usage1/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(1, usage()); + EXPECT_EQ(1, quota()); // should be clamped to our current usage + + GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(10, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://usage200/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(200, usage()); + EXPECT_EQ(kPerHostQuota, quota()); // should be clamped to the nominal quota +} + +TEST_F(QuotaManagerTest, GetTemporaryUsageAndQuota_Unlimited) { + static const MockOriginData kData[] = { + { "http://usage10/", kTemp, 10 }, + { "http://usage50/", kTemp, 50 }, + { "http://unlimited/", kTemp, 4000 }, + }; + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + // Test when not overbugdet. + SetTemporaryGlobalQuota(1000); + base::MessageLoop::current()->RunUntilIdle(); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(10 + 50 + 4000, usage()); + EXPECT_EQ(4000, unlimited_usage()); + + const int kPerHostQuotaFor1000 = + 1000 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(kPerHostQuotaFor1000, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(50, usage()); + EXPECT_EQ(kPerHostQuotaFor1000, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4000, usage()); + EXPECT_EQ(kAvailableSpaceForApp + usage(), quota()); + + GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(QuotaManager::kNoLimit, quota()); + + // Test when overbugdet. + SetTemporaryGlobalQuota(100); + base::MessageLoop::current()->RunUntilIdle(); + + const int kPerHostQuotaFor100 = + 100 / QuotaManager::kPerHostTemporaryPortion; + + GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(50, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4000, usage()); + EXPECT_EQ(kAvailableSpaceForApp + usage(), quota()); + + GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(QuotaManager::kNoLimit, quota()); + + // Revoke the unlimited rights and make sure the change is noticed. + mock_special_storage_policy()->Reset(); + mock_special_storage_policy()->NotifyCleared(); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(10 + 50 + 4000, usage()); + EXPECT_EQ(0, unlimited_usage()); + + GetUsageAndQuotaForWebApps(GURL("http://usage10/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(10, quota()); // should be clamped to our current usage + + GetUsageAndQuotaForWebApps(GURL("http://usage50/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(50, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4000, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); + + GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(4000, usage()); + EXPECT_EQ(kPerHostQuotaFor100, quota()); +} + +TEST_F(QuotaManagerTest, OriginInUse) { + const GURL kFooOrigin("http://foo.com/"); + const GURL kBarOrigin("http://bar.com/"); + + EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin)); + quota_manager()->NotifyOriginInUse(kFooOrigin); // count of 1 + EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin)); + quota_manager()->NotifyOriginInUse(kFooOrigin); // count of 2 + EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin)); + quota_manager()->NotifyOriginNoLongerInUse(kFooOrigin); // count of 1 + EXPECT_TRUE(quota_manager()->IsOriginInUse(kFooOrigin)); + + EXPECT_FALSE(quota_manager()->IsOriginInUse(kBarOrigin)); + quota_manager()->NotifyOriginInUse(kBarOrigin); + EXPECT_TRUE(quota_manager()->IsOriginInUse(kBarOrigin)); + quota_manager()->NotifyOriginNoLongerInUse(kBarOrigin); + EXPECT_FALSE(quota_manager()->IsOriginInUse(kBarOrigin)); + + quota_manager()->NotifyOriginNoLongerInUse(kFooOrigin); + EXPECT_FALSE(quota_manager()->IsOriginInUse(kFooOrigin)); +} + +TEST_F(QuotaManagerTest, GetAndSetPerststentHostQuota) { + RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem)); + + GetPersistentHostQuota("foo.com"); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, quota()); + + SetPersistentHostQuota("foo.com", 100); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(100, quota()); + + GetPersistentHostQuota("foo.com"); + SetPersistentHostQuota("foo.com", 200); + GetPersistentHostQuota("foo.com"); + SetPersistentHostQuota("foo.com", 300000000000ll); + GetPersistentHostQuota("foo.com"); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(300000000000ll, quota()); +} + +TEST_F(QuotaManagerTest, GetAndSetPersistentUsageAndQuota) { + RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem)); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(0, quota()); + + SetPersistentHostQuota("foo.com", 100); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(100, quota()); + + // For installed app GetUsageAndQuotaForWebApps returns the capped quota. + mock_special_storage_policy()->GrantQueryDiskSize(GURL("http://installed/")); + SetPersistentHostQuota("installed", kAvailableSpaceForApp + 100); + GetUsageAndQuotaForWebApps(GURL("http://installed/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kAvailableSpaceForApp, quota()); + + // Ditto for unlimited apps. + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); + GetUsageAndQuotaForWebApps(GURL("http://unlimited/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kAvailableSpaceForApp, quota()); + + // GetUsageAndQuotaForStorageClient should just return 0 usage and + // kNoLimit quota. + GetUsageAndQuotaForStorageClient(GURL("http://unlimited/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(0, usage()); + EXPECT_EQ(QuotaManager::kNoLimit, quota()); +} + +TEST_F(QuotaManagerTest, GetSyncableQuota) { + RegisterClient(CreateClient(NULL, 0, QuotaClient::kFileSystem)); + + // Pre-condition check: available disk space (for testing) is less than + // the default quota for syncable storage. + EXPECT_LE(kAvailableSpaceForApp, + QuotaManager::kSyncableStorageDefaultHostQuota); + + // For installed apps the quota manager should return + // kAvailableSpaceForApp as syncable quota (because of the pre-condition). + mock_special_storage_policy()->GrantQueryDiskSize(GURL("http://installed/")); + GetUsageAndQuotaForWebApps(GURL("http://installed/"), kSync); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(kAvailableSpaceForApp, quota()); + + // If it's not installed (which shouldn't happen in real case) it + // should just return the default host quota for syncable. + GetUsageAndQuotaForWebApps(GURL("http://foo/"), kSync); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, usage()); + EXPECT_EQ(QuotaManager::kSyncableStorageDefaultHostQuota, quota()); +} + +TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_MultiOrigins) { + static const MockOriginData kData[] = { + { "http://foo.com/", kPerm, 10 }, + { "http://foo.com:8080/", kPerm, 20 }, + { "https://foo.com/", kPerm, 13 }, + { "https://foo.com:8081/", kPerm, 19 }, + { "http://bar.com/", kPerm, 5 }, + { "https://bar.com/", kPerm, 7 }, + { "http://baz.com/", kPerm, 30 }, + { "http://foo.com/", kTemp, 40 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + + SetPersistentHostQuota("foo.com", 100); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20 + 13 + 19, usage()); + EXPECT_EQ(100, quota()); +} + +TEST_F(QuotaManagerTest, GetPersistentUsage_WithModify) { + GetUsage_WithModifyTestBody(kPerm); +} + +TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_WithAdditionalTasks) { + static const MockOriginData kData[] = { + { "http://foo.com/", kPerm, 10 }, + { "http://foo.com:8080/", kPerm, 20 }, + { "http://bar.com/", kPerm, 13 }, + { "http://foo.com/", kTemp, 40 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + SetPersistentHostQuota("foo.com", 100); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20, usage()); + EXPECT_EQ(100, quota()); + + set_additional_callback_count(0); + RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), + kPerm); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10 + 20, usage()); + EXPECT_EQ(2, additional_callback_count()); +} + +TEST_F(QuotaManagerTest, GetPersistentUsageAndQuota_NukeManager) { + static const MockOriginData kData[] = { + { "http://foo.com/", kPerm, 10 }, + { "http://foo.com:8080/", kPerm, 20 }, + { "http://bar.com/", kPerm, 13 }, + { "http://foo.com/", kTemp, 40 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + SetPersistentHostQuota("foo.com", 100); + + set_additional_callback_count(0); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + RunAdditionalUsageAndQuotaTask(GURL("http://foo.com/"), kPerm); + RunAdditionalUsageAndQuotaTask(GURL("http://bar.com/"), kPerm); + + // Nuke before waiting for callbacks. + set_quota_manager(NULL); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaErrorAbort, status()); +} + +TEST_F(QuotaManagerTest, GetUsage_Simple) { + static const MockOriginData kData[] = { + { "http://foo.com/", kPerm, 1 }, + { "http://foo.com:1/", kPerm, 20 }, + { "http://bar.com/", kTemp, 300 }, + { "https://buz.com/", kTemp, 4000 }, + { "http://buz.com/", kTemp, 50000 }, + { "http://bar.com:1/", kPerm, 600000 }, + { "http://foo.com/", kTemp, 7000000 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + + GetGlobalUsage(kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 1 + 20 + 600000); + EXPECT_EQ(0, unlimited_usage()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000); + EXPECT_EQ(0, unlimited_usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 1 + 20); + + GetHostUsage("buz.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 4000 + 50000); +} + +TEST_F(QuotaManagerTest, GetUsage_WithModification) { + static const MockOriginData kData[] = { + { "http://foo.com/", kPerm, 1 }, + { "http://foo.com:1/", kPerm, 20 }, + { "http://bar.com/", kTemp, 300 }, + { "https://buz.com/", kTemp, 4000 }, + { "http://buz.com/", kTemp, 50000 }, + { "http://bar.com:1/", kPerm, 600000 }, + { "http://foo.com/", kTemp, 7000000 }, + }; + + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GetGlobalUsage(kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 1 + 20 + 600000); + EXPECT_EQ(0, unlimited_usage()); + + client->ModifyOriginAndNotify( + GURL("http://foo.com/"), kPerm, 80000000); + + GetGlobalUsage(kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 1 + 20 + 600000 + 80000000); + EXPECT_EQ(0, unlimited_usage()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000); + EXPECT_EQ(0, unlimited_usage()); + + client->ModifyOriginAndNotify( + GURL("http://foo.com/"), kTemp, 1); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 300 + 4000 + 50000 + 7000000 + 1); + EXPECT_EQ(0, unlimited_usage()); + + GetHostUsage("buz.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 4000 + 50000); + + client->ModifyOriginAndNotify( + GURL("http://buz.com/"), kTemp, 900000000); + + GetHostUsage("buz.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(usage(), 4000 + 50000 + 900000000); +} + +TEST_F(QuotaManagerTest, GetUsage_WithDeleteOrigin) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://foo.com:1/", kTemp, 20 }, + { "http://foo.com/", kPerm, 300 }, + { "http://bar.com/", kTemp, 4000 }, + }; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_global_tmp = usage(); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_tmp = usage(); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_pers = usage(); + + DeleteClientOriginData(client, GURL("http://foo.com/"), + kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp - 1, usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_tmp - 1, usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_pers, usage()); +} + +TEST_F(QuotaManagerTest, GetAvailableSpaceTest) { + GetAvailableSpace(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_LE(0, available_space()); +} + +TEST_F(QuotaManagerTest, EvictOriginData) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://foo.com:1/", kTemp, 20 }, + { "http://foo.com/", kPerm, 300 }, + { "http://bar.com/", kTemp, 4000 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com/", kTemp, 50000 }, + { "http://foo.com:1/", kTemp, 6000 }, + { "http://foo.com/", kPerm, 700 }, + { "https://foo.com/", kTemp, 80 }, + { "http://bar.com/", kTemp, 9 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kDatabase); + RegisterClient(client1); + RegisterClient(client2); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_global_tmp = usage(); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_tmp = usage(); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_pers = usage(); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData1); ++i) + quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, + GURL(kData1[i].origin), kData1[i].type); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData2); ++i) + quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, + GURL(kData2[i].origin), kData2[i].type); + base::MessageLoop::current()->RunUntilIdle(); + + EvictOriginData(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + + DumpOriginInfoTable(); + base::MessageLoop::current()->RunUntilIdle(); + + typedef OriginInfoTableEntries::const_iterator iterator; + for (iterator itr(origin_info_entries().begin()), + end(origin_info_entries().end()); + itr != end; ++itr) { + if (itr->type == kTemp) + EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec()); + } + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp - (1 + 50000), usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_tmp - (1 + 50000), usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_pers, usage()); +} + +TEST_F(QuotaManagerTest, EvictOriginDataWithDeletionError) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://foo.com:1/", kTemp, 20 }, + { "http://foo.com/", kPerm, 300 }, + { "http://bar.com/", kTemp, 4000 }, + }; + static const int kNumberOfTemporaryOrigins = 3; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_global_tmp = usage(); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_tmp = usage(); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_pers = usage(); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData); ++i) + NotifyStorageAccessed(client, GURL(kData[i].origin), kData[i].type); + base::MessageLoop::current()->RunUntilIdle(); + + client->AddOriginToErrorSet(GURL("http://foo.com/"), kTemp); + + for (int i = 0; + i < QuotaManager::kThresholdOfErrorsToBeBlacklisted + 1; + ++i) { + EvictOriginData(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaErrorInvalidModification, status()); + } + + DumpOriginInfoTable(); + base::MessageLoop::current()->RunUntilIdle(); + + bool found_origin_in_database = false; + typedef OriginInfoTableEntries::const_iterator iterator; + for (iterator itr(origin_info_entries().begin()), + end(origin_info_entries().end()); + itr != end; ++itr) { + if (itr->type == kTemp && + GURL("http://foo.com/") == itr->origin) { + found_origin_in_database = true; + break; + } + } + // The origin "http://foo.com/" should be in the database. + EXPECT_TRUE(found_origin_in_database); + + for (size_t i = 0; i < kNumberOfTemporaryOrigins - 1; ++i) { + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_FALSE(lru_origin().is_empty()); + // The origin "http://foo.com/" should not be in the LRU list. + EXPECT_NE(std::string("http://foo.com/"), lru_origin().spec()); + DeleteOriginFromDatabase(lru_origin(), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + } + + // Now the LRU list must be empty. + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(lru_origin().is_empty()); + + // Deleting origins from the database should not affect the results of the + // following checks. + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp, usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_tmp, usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_pers, usage()); +} + +TEST_F(QuotaManagerTest, GetUsageAndQuotaForEviction) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://foo.com:1/", kTemp, 20 }, + { "http://foo.com/", kPerm, 300 }, + { "http://unlimited/", kTemp, 4000 }, + }; + + mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + SetTemporaryGlobalQuota(10000000); + base::MessageLoop::current()->RunUntilIdle(); + + GetUsageAndQuotaForEviction(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(21, limited_usage()); + EXPECT_EQ(10000000, quota()); + EXPECT_LE(0, available_space()); +} + +TEST_F(QuotaManagerTest, DeleteHostDataSimple) { + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 1 }, + }; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_global_tmp = usage(); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_tmp = usage(); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + int64 predelete_host_pers = usage(); + + DeleteHostData(std::string(), kTemp, kAllClients); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp, usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_tmp, usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_pers, usage()); + + DeleteHostData("foo.com", kTemp, kAllClients); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp - 1, usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_tmp - 1, usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_host_pers, usage()); +} + +TEST_F(QuotaManagerTest, DeleteHostDataMultiple) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://foo.com:1/", kTemp, 20 }, + { "http://foo.com/", kPerm, 300 }, + { "http://bar.com/", kTemp, 4000 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com/", kTemp, 50000 }, + { "http://foo.com:1/", kTemp, 6000 }, + { "http://foo.com/", kPerm, 700 }, + { "https://foo.com/", kTemp, 80 }, + { "http://bar.com/", kTemp, 9 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kDatabase); + RegisterClient(client1); + RegisterClient(client2); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_global_tmp = usage(); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_tmp = usage(); + + GetHostUsage("bar.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_bar_tmp = usage(); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_pers = usage(); + + GetHostUsage("bar.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_bar_pers = usage(); + + reset_status_callback_count(); + DeleteHostData("foo.com", kTemp, kAllClients); + DeleteHostData("bar.com", kTemp, kAllClients); + DeleteHostData("foo.com", kTemp, kAllClients); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(3, status_callback_count()); + + DumpOriginInfoTable(); + base::MessageLoop::current()->RunUntilIdle(); + + typedef OriginInfoTableEntries::const_iterator iterator; + for (iterator itr(origin_info_entries().begin()), + end(origin_info_entries().end()); + itr != end; ++itr) { + if (itr->type == kTemp) { + EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec()); + EXPECT_NE(std::string("http://foo.com:1/"), itr->origin.spec()); + EXPECT_NE(std::string("https://foo.com/"), itr->origin.spec()); + EXPECT_NE(std::string("http://bar.com/"), itr->origin.spec()); + } + } + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp - (1 + 20 + 4000 + 50000 + 6000 + 80 + 9), + usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - (1 + 20 + 50000 + 6000 + 80), usage()); + + GetHostUsage("bar.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_pers, usage()); + + GetHostUsage("bar.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_bar_pers, usage()); +} + +// Single-run DeleteOriginData cases must be well covered by +// EvictOriginData tests. +TEST_F(QuotaManagerTest, DeleteOriginDataMultiple) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 1 }, + { "http://foo.com:1/", kTemp, 20 }, + { "http://foo.com/", kPerm, 300 }, + { "http://bar.com/", kTemp, 4000 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com/", kTemp, 50000 }, + { "http://foo.com:1/", kTemp, 6000 }, + { "http://foo.com/", kPerm, 700 }, + { "https://foo.com/", kTemp, 80 }, + { "http://bar.com/", kTemp, 9 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kDatabase); + RegisterClient(client1); + RegisterClient(client2); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_global_tmp = usage(); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_tmp = usage(); + + GetHostUsage("bar.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_bar_tmp = usage(); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_pers = usage(); + + GetHostUsage("bar.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_bar_pers = usage(); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData1); ++i) + quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, + GURL(kData1[i].origin), kData1[i].type); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData2); ++i) + quota_manager()->NotifyStorageAccessed(QuotaClient::kUnknown, + GURL(kData2[i].origin), kData2[i].type); + base::MessageLoop::current()->RunUntilIdle(); + + reset_status_callback_count(); + DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients); + DeleteOriginData(GURL("http://bar.com/"), kTemp, kAllClients); + DeleteOriginData(GURL("http://foo.com/"), kTemp, kAllClients); + base::MessageLoop::current()->RunUntilIdle(); + + EXPECT_EQ(3, status_callback_count()); + + DumpOriginInfoTable(); + base::MessageLoop::current()->RunUntilIdle(); + + typedef OriginInfoTableEntries::const_iterator iterator; + for (iterator itr(origin_info_entries().begin()), + end(origin_info_entries().end()); + itr != end; ++itr) { + if (itr->type == kTemp) { + EXPECT_NE(std::string("http://foo.com/"), itr->origin.spec()); + EXPECT_NE(std::string("http://bar.com/"), itr->origin.spec()); + } + } + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_global_tmp - (1 + 4000 + 50000 + 9), usage()); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - (1 + 50000), usage()); + + GetHostUsage("bar.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_bar_tmp - (4000 + 9), usage()); + + GetHostUsage("foo.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_pers, usage()); + + GetHostUsage("bar.com", kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_bar_pers, usage()); +} + +TEST_F(QuotaManagerTest, GetCachedOrigins) { + static const MockOriginData kData[] = { + { "http://a.com/", kTemp, 1 }, + { "http://a.com:1/", kTemp, 20 }, + { "http://b.com/", kPerm, 300 }, + { "http://c.com/", kTemp, 4000 }, + }; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + // TODO(kinuko): Be careful when we add cache pruner. + + std::set<GURL> origins; + GetCachedOrigins(kTemp, &origins); + EXPECT_TRUE(origins.empty()); + + // No matter how we make queries the quota manager tries to cache all + // the origins at startup. + GetHostUsage("a.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + GetCachedOrigins(kTemp, &origins); + EXPECT_EQ(3U, origins.size()); + + GetHostUsage("b.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + GetCachedOrigins(kTemp, &origins); + EXPECT_EQ(3U, origins.size()); + + GetCachedOrigins(kPerm, &origins); + EXPECT_TRUE(origins.empty()); + + GetGlobalUsage(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + GetCachedOrigins(kTemp, &origins); + EXPECT_EQ(3U, origins.size()); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData); ++i) { + if (kData[i].type == kTemp) + EXPECT_TRUE(origins.find(GURL(kData[i].origin)) != origins.end()); + } +} + +TEST_F(QuotaManagerTest, NotifyAndLRUOrigin) { + static const MockOriginData kData[] = { + { "http://a.com/", kTemp, 0 }, + { "http://a.com:1/", kTemp, 0 }, + { "https://a.com/", kTemp, 0 }, + { "http://b.com/", kPerm, 0 }, // persistent + { "http://c.com/", kTemp, 0 }, + }; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GURL origin; + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(lru_origin().is_empty()); + + NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp); + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("http://a.com/", lru_origin().spec()); + + NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm); + NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp); + NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp); + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("http://a.com/", lru_origin().spec()); + + DeleteOriginFromDatabase(lru_origin(), kTemp); + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("https://a.com/", lru_origin().spec()); + + DeleteOriginFromDatabase(lru_origin(), kTemp); + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("http://c.com/", lru_origin().spec()); +} + +TEST_F(QuotaManagerTest, GetLRUOriginWithOriginInUse) { + static const MockOriginData kData[] = { + { "http://a.com/", kTemp, 0 }, + { "http://a.com:1/", kTemp, 0 }, + { "https://a.com/", kTemp, 0 }, + { "http://b.com/", kPerm, 0 }, // persistent + { "http://c.com/", kTemp, 0 }, + }; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GURL origin; + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(lru_origin().is_empty()); + + NotifyStorageAccessed(client, GURL("http://a.com/"), kTemp); + NotifyStorageAccessed(client, GURL("http://b.com/"), kPerm); + NotifyStorageAccessed(client, GURL("https://a.com/"), kTemp); + NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp); + + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("http://a.com/", lru_origin().spec()); + + // Notify origin http://a.com is in use. + NotifyOriginInUse(GURL("http://a.com/")); + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("https://a.com/", lru_origin().spec()); + + // Notify origin https://a.com is in use while GetLRUOrigin is running. + GetLRUOrigin(kTemp); + NotifyOriginInUse(GURL("https://a.com/")); + base::MessageLoop::current()->RunUntilIdle(); + // Post-filtering must have excluded the returned origin, so we will + // see empty result here. + EXPECT_TRUE(lru_origin().is_empty()); + + // Notify access for http://c.com while GetLRUOrigin is running. + GetLRUOrigin(kTemp); + NotifyStorageAccessed(client, GURL("http://c.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + // Post-filtering must have excluded the returned origin, so we will + // see empty result here. + EXPECT_TRUE(lru_origin().is_empty()); + + NotifyOriginNoLongerInUse(GURL("http://a.com/")); + NotifyOriginNoLongerInUse(GURL("https://a.com/")); + GetLRUOrigin(kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ("http://a.com/", lru_origin().spec()); +} + +TEST_F(QuotaManagerTest, GetOriginsModifiedSince) { + static const MockOriginData kData[] = { + { "http://a.com/", kTemp, 0 }, + { "http://a.com:1/", kTemp, 0 }, + { "https://a.com/", kTemp, 0 }, + { "http://b.com/", kPerm, 0 }, // persistent + { "http://c.com/", kTemp, 0 }, + }; + MockStorageClient* client = CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem); + RegisterClient(client); + + GetOriginsModifiedSince(kTemp, base::Time()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(modified_origins().empty()); + EXPECT_EQ(modified_origins_type(), kTemp); + + base::Time time1 = client->IncrementMockTime(); + client->ModifyOriginAndNotify(GURL("http://a.com/"), kTemp, 10); + client->ModifyOriginAndNotify(GURL("http://a.com:1/"), kTemp, 10); + client->ModifyOriginAndNotify(GURL("http://b.com/"), kPerm, 10); + base::Time time2 = client->IncrementMockTime(); + client->ModifyOriginAndNotify(GURL("https://a.com/"), kTemp, 10); + client->ModifyOriginAndNotify(GURL("http://c.com/"), kTemp, 10); + base::Time time3 = client->IncrementMockTime(); + + GetOriginsModifiedSince(kTemp, time1); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(4U, modified_origins().size()); + EXPECT_EQ(modified_origins_type(), kTemp); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kData); ++i) { + if (kData[i].type == kTemp) + EXPECT_EQ(1U, modified_origins().count(GURL(kData[i].origin))); + } + + GetOriginsModifiedSince(kTemp, time2); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(2U, modified_origins().size()); + + GetOriginsModifiedSince(kTemp, time3); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_TRUE(modified_origins().empty()); + EXPECT_EQ(modified_origins_type(), kTemp); + + client->ModifyOriginAndNotify(GURL("http://a.com/"), kTemp, 10); + + GetOriginsModifiedSince(kTemp, time3); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(1U, modified_origins().size()); + EXPECT_EQ(1U, modified_origins().count(GURL("http://a.com/"))); + EXPECT_EQ(modified_origins_type(), kTemp); +} + +TEST_F(QuotaManagerTest, DumpQuotaTable) { + SetPersistentHostQuota("example1.com", 1); + SetPersistentHostQuota("example2.com", 20); + SetPersistentHostQuota("example3.com", 300); + base::MessageLoop::current()->RunUntilIdle(); + + DumpQuotaTable(); + base::MessageLoop::current()->RunUntilIdle(); + + const QuotaTableEntry kEntries[] = { + QuotaTableEntry("example1.com", kPerm, 1), + QuotaTableEntry("example2.com", kPerm, 20), + QuotaTableEntry("example3.com", kPerm, 300), + }; + std::set<QuotaTableEntry> entries + (kEntries, kEntries + ARRAYSIZE_UNSAFE(kEntries)); + + typedef QuotaTableEntries::const_iterator iterator; + for (iterator itr(quota_entries().begin()), end(quota_entries().end()); + itr != end; ++itr) { + SCOPED_TRACE(testing::Message() + << "host = " << itr->host << ", " + << "quota = " << itr->quota); + EXPECT_EQ(1u, entries.erase(*itr)); + } + EXPECT_TRUE(entries.empty()); +} + +TEST_F(QuotaManagerTest, DumpOriginInfoTable) { + using std::make_pair; + + quota_manager()->NotifyStorageAccessed( + QuotaClient::kUnknown, + GURL("http://example.com/"), + kTemp); + quota_manager()->NotifyStorageAccessed( + QuotaClient::kUnknown, + GURL("http://example.com/"), + kPerm); + quota_manager()->NotifyStorageAccessed( + QuotaClient::kUnknown, + GURL("http://example.com/"), + kPerm); + base::MessageLoop::current()->RunUntilIdle(); + + DumpOriginInfoTable(); + base::MessageLoop::current()->RunUntilIdle(); + + typedef std::pair<GURL, StorageType> TypedOrigin; + typedef std::pair<TypedOrigin, int> Entry; + const Entry kEntries[] = { + make_pair(make_pair(GURL("http://example.com/"), kTemp), 1), + make_pair(make_pair(GURL("http://example.com/"), kPerm), 2), + }; + std::set<Entry> entries + (kEntries, kEntries + ARRAYSIZE_UNSAFE(kEntries)); + + typedef OriginInfoTableEntries::const_iterator iterator; + for (iterator itr(origin_info_entries().begin()), + end(origin_info_entries().end()); + itr != end; ++itr) { + SCOPED_TRACE(testing::Message() + << "host = " << itr->origin << ", " + << "type = " << itr->type << ", " + << "used_count = " << itr->used_count); + EXPECT_EQ(1u, entries.erase( + make_pair(make_pair(itr->origin, itr->type), + itr->used_count))); + } + EXPECT_TRUE(entries.empty()); +} + +TEST_F(QuotaManagerTest, QuotaForEmptyHost) { + GetPersistentHostQuota(std::string()); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(0, quota()); + + SetPersistentHostQuota(std::string(), 10); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaErrorNotSupported, status()); +} + +TEST_F(QuotaManagerTest, DeleteSpecificClientTypeSingleOrigin) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 1 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com/", kTemp, 2 }, + }; + static const MockOriginData kData3[] = { + { "http://foo.com/", kTemp, 4 }, + }; + static const MockOriginData kData4[] = { + { "http://foo.com/", kTemp, 8 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kAppcache); + MockStorageClient* client3 = CreateClient(kData3, ARRAYSIZE_UNSAFE(kData3), + QuotaClient::kDatabase); + MockStorageClient* client4 = CreateClient(kData4, ARRAYSIZE_UNSAFE(kData4), + QuotaClient::kIndexedDatabase); + RegisterClient(client1); + RegisterClient(client2); + RegisterClient(client3); + RegisterClient(client4); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_tmp = usage(); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kFileSystem); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 1, usage()); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kAppcache); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage()); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, QuotaClient::kDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage()); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, + QuotaClient::kIndexedDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); +} + +TEST_F(QuotaManagerTest, DeleteSpecificClientTypeSingleHost) { + static const MockOriginData kData1[] = { + { "http://foo.com:1111/", kTemp, 1 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com:2222/", kTemp, 2 }, + }; + static const MockOriginData kData3[] = { + { "http://foo.com:3333/", kTemp, 4 }, + }; + static const MockOriginData kData4[] = { + { "http://foo.com:4444/", kTemp, 8 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kAppcache); + MockStorageClient* client3 = CreateClient(kData3, ARRAYSIZE_UNSAFE(kData3), + QuotaClient::kDatabase); + MockStorageClient* client4 = CreateClient(kData4, ARRAYSIZE_UNSAFE(kData4), + QuotaClient::kIndexedDatabase); + RegisterClient(client1); + RegisterClient(client2); + RegisterClient(client3); + RegisterClient(client4); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_tmp = usage(); + + DeleteHostData("foo.com", kTemp, QuotaClient::kFileSystem); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 1, usage()); + + DeleteHostData("foo.com", kTemp, QuotaClient::kAppcache); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage()); + + DeleteHostData("foo.com", kTemp, QuotaClient::kDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 4 - 2 - 1, usage()); + + DeleteHostData("foo.com", kTemp, QuotaClient::kIndexedDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); +} + +TEST_F(QuotaManagerTest, DeleteMultipleClientTypesSingleOrigin) { + static const MockOriginData kData1[] = { + { "http://foo.com/", kTemp, 1 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com/", kTemp, 2 }, + }; + static const MockOriginData kData3[] = { + { "http://foo.com/", kTemp, 4 }, + }; + static const MockOriginData kData4[] = { + { "http://foo.com/", kTemp, 8 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kAppcache); + MockStorageClient* client3 = CreateClient(kData3, ARRAYSIZE_UNSAFE(kData3), + QuotaClient::kDatabase); + MockStorageClient* client4 = CreateClient(kData4, ARRAYSIZE_UNSAFE(kData4), + QuotaClient::kIndexedDatabase); + RegisterClient(client1); + RegisterClient(client2); + RegisterClient(client3); + RegisterClient(client4); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_tmp = usage(); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, + QuotaClient::kFileSystem | QuotaClient::kDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 4 - 1, usage()); + + DeleteOriginData(GURL("http://foo.com/"), kTemp, + QuotaClient::kAppcache | QuotaClient::kIndexedDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); +} + +TEST_F(QuotaManagerTest, DeleteMultipleClientTypesSingleHost) { + static const MockOriginData kData1[] = { + { "http://foo.com:1111/", kTemp, 1 }, + }; + static const MockOriginData kData2[] = { + { "http://foo.com:2222/", kTemp, 2 }, + }; + static const MockOriginData kData3[] = { + { "http://foo.com:3333/", kTemp, 4 }, + }; + static const MockOriginData kData4[] = { + { "http://foo.com:4444/", kTemp, 8 }, + }; + MockStorageClient* client1 = CreateClient(kData1, ARRAYSIZE_UNSAFE(kData1), + QuotaClient::kFileSystem); + MockStorageClient* client2 = CreateClient(kData2, ARRAYSIZE_UNSAFE(kData2), + QuotaClient::kAppcache); + MockStorageClient* client3 = CreateClient(kData3, ARRAYSIZE_UNSAFE(kData3), + QuotaClient::kDatabase); + MockStorageClient* client4 = CreateClient(kData4, ARRAYSIZE_UNSAFE(kData4), + QuotaClient::kIndexedDatabase); + RegisterClient(client1); + RegisterClient(client2); + RegisterClient(client3); + RegisterClient(client4); + + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + const int64 predelete_foo_tmp = usage(); + + DeleteHostData("foo.com", kTemp, + QuotaClient::kFileSystem | QuotaClient::kAppcache); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 2 - 1, usage()); + + DeleteHostData("foo.com", kTemp, + QuotaClient::kDatabase | QuotaClient::kIndexedDatabase); + base::MessageLoop::current()->RunUntilIdle(); + GetHostUsage("foo.com", kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(predelete_foo_tmp - 8 - 4 - 2 - 1, usage()); +} + +TEST_F(QuotaManagerTest, GetUsageAndQuota_Incognito) { + ResetQuotaManager(true); + + static const MockOriginData kData[] = { + { "http://foo.com/", kTemp, 10 }, + { "http://foo.com/", kPerm, 80 }, + }; + RegisterClient(CreateClient(kData, ARRAYSIZE_UNSAFE(kData), + QuotaClient::kFileSystem)); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(80, usage()); + EXPECT_EQ(0, quota()); + + SetTemporaryGlobalQuota(100); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_LE(std::min(static_cast<int64>(100 / kPerHostTemporaryPortion), + QuotaManager::kIncognitoDefaultQuotaLimit), quota()); + + mock_special_storage_policy()->AddUnlimited(GURL("http://foo.com/")); + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kPerm); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(80, usage()); + EXPECT_EQ(QuotaManager::kIncognitoDefaultQuotaLimit, quota()); + + GetUsageAndQuotaForWebApps(GURL("http://foo.com/"), kTemp); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(kQuotaStatusOk, status()); + EXPECT_EQ(10, usage()); + EXPECT_EQ(QuotaManager::kIncognitoDefaultQuotaLimit, quota()); +} + +} // namespace quota diff --git a/webkit/browser/quota/quota_task.cc b/webkit/browser/quota/quota_task.cc new file mode 100644 index 0000000..95b02f1 --- /dev/null +++ b/webkit/browser/quota/quota_task.cc @@ -0,0 +1,79 @@ +// Copyright 2013 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 "webkit/browser/quota/quota_task.h" + +#include <algorithm> +#include <functional> + +#include "base/bind.h" +#include "base/location.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "base/single_thread_task_runner.h" + +using base::TaskRunner; + +namespace quota { + +// QuotaTask --------------------------------------------------------------- + +QuotaTask::~QuotaTask() { +} + +void QuotaTask::Start() { + DCHECK(observer_); + observer()->RegisterTask(this); + Run(); +} + +QuotaTask::QuotaTask(QuotaTaskObserver* observer) + : observer_(observer), + original_task_runner_(base::MessageLoopProxy::current()), + delete_scheduled_(false) { +} + +void QuotaTask::CallCompleted() { + DCHECK(original_task_runner_->BelongsToCurrentThread()); + if (observer_) { + observer_->UnregisterTask(this); + Completed(); + } +} + +void QuotaTask::Abort() { + DCHECK(original_task_runner_->BelongsToCurrentThread()); + observer_ = NULL; + Aborted(); +} + +void QuotaTask::DeleteSoon() { + DCHECK(original_task_runner_->BelongsToCurrentThread()); + if (delete_scheduled_) + return; + delete_scheduled_ = true; + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +// QuotaTaskObserver ------------------------------------------------------- + +QuotaTaskObserver::~QuotaTaskObserver() { + std::for_each(running_quota_tasks_.begin(), + running_quota_tasks_.end(), + std::mem_fun(&QuotaTask::Abort)); +} + +QuotaTaskObserver::QuotaTaskObserver() { +} + +void QuotaTaskObserver::RegisterTask(QuotaTask* task) { + running_quota_tasks_.insert(task); +} + +void QuotaTaskObserver::UnregisterTask(QuotaTask* task) { + DCHECK(running_quota_tasks_.find(task) != running_quota_tasks_.end()); + running_quota_tasks_.erase(task); +} + +} // namespace quota diff --git a/webkit/browser/quota/quota_task.h b/webkit/browser/quota/quota_task.h new file mode 100644 index 0000000..0f245d6 --- /dev/null +++ b/webkit/browser/quota/quota_task.h @@ -0,0 +1,79 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_QUOTA_TASK_H_ +#define WEBKIT_BROWSER_QUOTA_QUOTA_TASK_H_ + +#include <set> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner_helpers.h" +#include "webkit/storage/webkit_storage_export.h" + +namespace base { +class SingleThreadTaskRunner; +class TaskRunner; +} + +namespace quota { + +class QuotaTaskObserver; + +// A base class for quota tasks. +// TODO(kinuko): Revise this using base::Callback. +class QuotaTask { + public: + void Start(); + + protected: + explicit QuotaTask(QuotaTaskObserver* observer); + virtual ~QuotaTask(); + + // The task body. + virtual void Run() = 0; + + // Called upon completion, on the original message loop. + virtual void Completed() = 0; + + // Called when the task is aborted. + virtual void Aborted() {} + + void CallCompleted(); + + // Call this to delete itself. + void DeleteSoon(); + + QuotaTaskObserver* observer() const { return observer_; } + base::SingleThreadTaskRunner* original_task_runner() const { + return original_task_runner_; + } + + private: + friend class base::DeleteHelper<QuotaTask>; + friend class QuotaTaskObserver; + + void Abort(); + QuotaTaskObserver* observer_; + scoped_refptr<base::SingleThreadTaskRunner> original_task_runner_; + bool delete_scheduled_; +}; + +class WEBKIT_STORAGE_EXPORT QuotaTaskObserver { + protected: + friend class QuotaTask; + + QuotaTaskObserver(); + virtual ~QuotaTaskObserver(); + + void RegisterTask(QuotaTask* task); + void UnregisterTask(QuotaTask* task); + + typedef std::set<QuotaTask*> TaskSet; + TaskSet running_quota_tasks_; +}; +} + +#endif // WEBKIT_BROWSER_QUOTA_QUOTA_TASK_H_ diff --git a/webkit/browser/quota/quota_temporary_storage_evictor.cc b/webkit/browser/quota/quota_temporary_storage_evictor.cc new file mode 100644 index 0000000..c0842a3 --- /dev/null +++ b/webkit/browser/quota/quota_temporary_storage_evictor.cc @@ -0,0 +1,261 @@ +// Copyright 2013 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 "webkit/browser/quota/quota_temporary_storage_evictor.h" + +#include <algorithm> + +#include "base/bind.h" +#include "base/metrics/histogram.h" +#include "googleurl/src/gurl.h" +#include "webkit/browser/quota/quota_manager.h" + +#define UMA_HISTOGRAM_MBYTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_COUNTS( \ + (name), static_cast<int>((sample) / kMBytes), \ + 1, 10 * 1024 * 1024 /* 10TB */, 100) + +#define UMA_HISTOGRAM_MINUTES(name, sample) \ + UMA_HISTOGRAM_CUSTOM_TIMES( \ + (name), (sample), \ + base::TimeDelta::FromMinutes(1), \ + base::TimeDelta::FromDays(1), 50) + +namespace { +const int64 kMBytes = 1024 * 1024; +const double kUsageRatioToStartEviction = 0.7; +const int kThresholdOfErrorsToStopEviction = 5; +const int kHistogramReportIntervalMinutes = 60; +} + +namespace quota { + +const int QuotaTemporaryStorageEvictor:: + kMinAvailableDiskSpaceToStartEvictionNotSpecified = -1; + +QuotaTemporaryStorageEvictor::EvictionRoundStatistics::EvictionRoundStatistics() + : in_round(false), + is_initialized(false), + usage_overage_at_round(-1), + diskspace_shortage_at_round(-1), + usage_on_beginning_of_round(-1), + usage_on_end_of_round(-1), + num_evicted_origins_in_round(0) { +} + +QuotaTemporaryStorageEvictor::QuotaTemporaryStorageEvictor( + QuotaEvictionHandler* quota_eviction_handler, + int64 interval_ms) + : min_available_disk_space_to_start_eviction_( + kMinAvailableDiskSpaceToStartEvictionNotSpecified), + quota_eviction_handler_(quota_eviction_handler), + interval_ms_(interval_ms), + repeated_eviction_(true), + weak_factory_(this) { + DCHECK(quota_eviction_handler); +} + +QuotaTemporaryStorageEvictor::~QuotaTemporaryStorageEvictor() { +} + +void QuotaTemporaryStorageEvictor::GetStatistics( + std::map<std::string, int64>* statistics) { + DCHECK(statistics); + + (*statistics)["errors-on-evicting-origin"] = + statistics_.num_errors_on_evicting_origin; + (*statistics)["errors-on-getting-usage-and-quota"] = + statistics_.num_errors_on_getting_usage_and_quota; + (*statistics)["evicted-origins"] = + statistics_.num_evicted_origins; + (*statistics)["eviction-rounds"] = + statistics_.num_eviction_rounds; + (*statistics)["skipped-eviction-rounds"] = + statistics_.num_skipped_eviction_rounds; +} + +void QuotaTemporaryStorageEvictor::ReportPerRoundHistogram() { + DCHECK(round_statistics_.in_round); + DCHECK(round_statistics_.is_initialized); + + base::Time now = base::Time::Now(); + UMA_HISTOGRAM_TIMES("Quota.TimeSpentToAEvictionRound", + now - round_statistics_.start_time); + if (!time_of_end_of_last_round_.is_null()) + UMA_HISTOGRAM_MINUTES("Quota.TimeDeltaOfEvictionRounds", + now - time_of_end_of_last_round_); + UMA_HISTOGRAM_MBYTES("Quota.UsageOverageOfTemporaryGlobalStorage", + round_statistics_.usage_overage_at_round); + UMA_HISTOGRAM_MBYTES("Quota.DiskspaceShortage", + round_statistics_.diskspace_shortage_at_round); + UMA_HISTOGRAM_MBYTES("Quota.EvictedBytesPerRound", + round_statistics_.usage_on_beginning_of_round - + round_statistics_.usage_on_end_of_round); + UMA_HISTOGRAM_COUNTS("Quota.NumberOfEvictedOriginsPerRound", + round_statistics_.num_evicted_origins_in_round); +} + +void QuotaTemporaryStorageEvictor::ReportPerHourHistogram() { + Statistics stats_in_hour(statistics_); + stats_in_hour.subtract_assign(previous_statistics_); + previous_statistics_ = statistics_; + + UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnEvictingOriginPerHour", + stats_in_hour.num_errors_on_evicting_origin); + UMA_HISTOGRAM_COUNTS("Quota.ErrorsOnGettingUsageAndQuotaPerHour", + stats_in_hour.num_errors_on_getting_usage_and_quota); + UMA_HISTOGRAM_COUNTS("Quota.EvictedOriginsPerHour", + stats_in_hour.num_evicted_origins); + UMA_HISTOGRAM_COUNTS("Quota.EvictionRoundsPerHour", + stats_in_hour.num_eviction_rounds); + UMA_HISTOGRAM_COUNTS("Quota.SkippedEvictionRoundsPerHour", + stats_in_hour.num_skipped_eviction_rounds); +} + +void QuotaTemporaryStorageEvictor::OnEvictionRoundStarted() { + if (round_statistics_.in_round) + return; + round_statistics_.in_round = true; + round_statistics_.start_time = base::Time::Now(); + ++statistics_.num_eviction_rounds; +} + +void QuotaTemporaryStorageEvictor::OnEvictionRoundFinished() { + // Check if skipped round + if (round_statistics_.num_evicted_origins_in_round) { + ReportPerRoundHistogram(); + time_of_end_of_last_nonskipped_round_ = base::Time::Now(); + } else { + ++statistics_.num_skipped_eviction_rounds; + } + // Reset stats for next round. + round_statistics_ = EvictionRoundStatistics(); +} + +void QuotaTemporaryStorageEvictor::Start() { + DCHECK(CalledOnValidThread()); + StartEvictionTimerWithDelay(0); + + if (histogram_timer_.IsRunning()) + return; + + histogram_timer_.Start( + FROM_HERE, base::TimeDelta::FromMinutes(kHistogramReportIntervalMinutes), + this, &QuotaTemporaryStorageEvictor::ReportPerHourHistogram); +} + +void QuotaTemporaryStorageEvictor::StartEvictionTimerWithDelay(int delay_ms) { + if (eviction_timer_.IsRunning()) + return; + eviction_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(delay_ms), + this, &QuotaTemporaryStorageEvictor::ConsiderEviction); +} + +void QuotaTemporaryStorageEvictor::ConsiderEviction() { + OnEvictionRoundStarted(); + + // Get usage and disk space, then continue. + quota_eviction_handler_->GetUsageAndQuotaForEviction( + base::Bind(&QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction, + weak_factory_.GetWeakPtr())); +} + +void QuotaTemporaryStorageEvictor::OnGotUsageAndQuotaForEviction( + QuotaStatusCode status, + const UsageAndQuota& qau) { + DCHECK(CalledOnValidThread()); + + int64 usage = qau.global_limited_usage; + DCHECK_GE(usage, 0); + + if (status != kQuotaStatusOk) + ++statistics_.num_errors_on_getting_usage_and_quota; + + int64 usage_overage = std::max( + static_cast<int64>(0), + usage - static_cast<int64>(qau.quota * kUsageRatioToStartEviction)); + + // min_available_disk_space_to_start_eviction_ might be < 0 if no value + // is explicitly configured yet. + int64 diskspace_shortage = std::max( + static_cast<int64>(0), + min_available_disk_space_to_start_eviction_ - qau.available_disk_space); + + if (!round_statistics_.is_initialized) { + round_statistics_.usage_overage_at_round = usage_overage; + round_statistics_.diskspace_shortage_at_round = diskspace_shortage; + round_statistics_.usage_on_beginning_of_round = usage; + round_statistics_.is_initialized = true; + } + round_statistics_.usage_on_end_of_round = usage; + + int64 amount_to_evict = std::max(usage_overage, diskspace_shortage); + if (status == kQuotaStatusOk && amount_to_evict > 0) { + // Space is getting tight. Get the least recently used origin and continue. + // TODO(michaeln): if the reason for eviction is low physical disk space, + // make 'unlimited' origins subject to eviction too. + quota_eviction_handler_->GetLRUOrigin( + kStorageTypeTemporary, + base::Bind(&QuotaTemporaryStorageEvictor::OnGotLRUOrigin, + weak_factory_.GetWeakPtr())); + } else { + if (repeated_eviction_) { + // No action required, sleep for a while and check again later. + if (statistics_.num_errors_on_getting_usage_and_quota < + kThresholdOfErrorsToStopEviction) { + StartEvictionTimerWithDelay(interval_ms_); + } else { + // TODO(dmikurube): Try restarting eviction after a while. + LOG(WARNING) << "Stopped eviction of temporary storage due to errors " + "in GetUsageAndQuotaForEviction."; + } + } + OnEvictionRoundFinished(); + } + + // TODO(dmikurube): Add error handling for the case status != kQuotaStatusOk. +} + +void QuotaTemporaryStorageEvictor::OnGotLRUOrigin(const GURL& origin) { + DCHECK(CalledOnValidThread()); + + if (origin.is_empty()) { + if (repeated_eviction_) + StartEvictionTimerWithDelay(interval_ms_); + OnEvictionRoundFinished(); + return; + } + + quota_eviction_handler_->EvictOriginData(origin, kStorageTypeTemporary, + base::Bind( + &QuotaTemporaryStorageEvictor::OnEvictionComplete, + weak_factory_.GetWeakPtr())); +} + +void QuotaTemporaryStorageEvictor::OnEvictionComplete( + QuotaStatusCode status) { + DCHECK(CalledOnValidThread()); + + // Just calling ConsiderEviction() or StartEvictionTimerWithDelay() here is + // ok. No need to deal with the case that all of the Delete operations fail + // for a certain origin. It doesn't result in trying to evict the same + // origin permanently. The evictor skips origins which had deletion errors + // a few times. + + if (status == kQuotaStatusOk) { + ++statistics_.num_evicted_origins; + ++round_statistics_.num_evicted_origins_in_round; + // We many need to get rid of more space so reconsider immediately. + ConsiderEviction(); + } else { + ++statistics_.num_errors_on_evicting_origin; + if (repeated_eviction_) { + // Sleep for a while and retry again until we see too many errors. + StartEvictionTimerWithDelay(interval_ms_); + } + OnEvictionRoundFinished(); + } +} + +} // namespace quota diff --git a/webkit/browser/quota/quota_temporary_storage_evictor.h b/webkit/browser/quota/quota_temporary_storage_evictor.h new file mode 100644 index 0000000..a4833b4 --- /dev/null +++ b/webkit/browser/quota/quota_temporary_storage_evictor.h @@ -0,0 +1,130 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_ +#define WEBKIT_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_ + +#include <map> +#include <string> + +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/timer.h" +#include "webkit/common/quota/quota_types.h" +#include "webkit/storage/webkit_storage_export.h" + +class GURL; + +namespace quota { + +class QuotaEvictionHandler; +struct UsageAndQuota; + +class WEBKIT_STORAGE_EXPORT_PRIVATE QuotaTemporaryStorageEvictor + : public base::NonThreadSafe { + public: + struct Statistics { + Statistics() + : num_errors_on_evicting_origin(0), + num_errors_on_getting_usage_and_quota(0), + num_evicted_origins(0), + num_eviction_rounds(0), + num_skipped_eviction_rounds(0) {} + int64 num_errors_on_evicting_origin; + int64 num_errors_on_getting_usage_and_quota; + int64 num_evicted_origins; + int64 num_eviction_rounds; + int64 num_skipped_eviction_rounds; + + void subtract_assign(const Statistics& rhs) { + num_errors_on_evicting_origin -= rhs.num_errors_on_evicting_origin; + num_errors_on_getting_usage_and_quota -= + rhs.num_errors_on_getting_usage_and_quota; + num_evicted_origins -= rhs.num_evicted_origins; + num_eviction_rounds -= rhs.num_eviction_rounds; + num_skipped_eviction_rounds -= rhs.num_skipped_eviction_rounds; + } + }; + + struct EvictionRoundStatistics { + EvictionRoundStatistics(); + + bool in_round; + bool is_initialized; + + base::Time start_time; + int64 usage_overage_at_round; + int64 diskspace_shortage_at_round; + + int64 usage_on_beginning_of_round; + int64 usage_on_end_of_round; + int64 num_evicted_origins_in_round; + }; + + QuotaTemporaryStorageEvictor( + QuotaEvictionHandler* quota_eviction_handler, + int64 interval_ms); + virtual ~QuotaTemporaryStorageEvictor(); + + void GetStatistics(std::map<std::string, int64>* statistics); + void ReportPerRoundHistogram(); + void ReportPerHourHistogram(); + void Start(); + + int64 min_available_disk_space_to_start_eviction() { + return min_available_disk_space_to_start_eviction_; + } + void reset_min_available_disk_space_to_start_eviction() { + min_available_disk_space_to_start_eviction_ = + kMinAvailableDiskSpaceToStartEvictionNotSpecified; + } + void set_min_available_disk_space_to_start_eviction(int64 value) { + min_available_disk_space_to_start_eviction_ = value; + } + + private: + friend class QuotaTemporaryStorageEvictorTest; + + void StartEvictionTimerWithDelay(int delay_ms); + void ConsiderEviction(); + void OnGotUsageAndQuotaForEviction( + QuotaStatusCode status, + const UsageAndQuota& quota_and_usage); + void OnGotLRUOrigin(const GURL& origin); + void OnEvictionComplete(QuotaStatusCode status); + + void OnEvictionRoundStarted(); + void OnEvictionRoundFinished(); + + // This is only used for tests. + void set_repeated_eviction(bool repeated_eviction) { + repeated_eviction_ = repeated_eviction; + } + + static const int kMinAvailableDiskSpaceToStartEvictionNotSpecified; + + int64 min_available_disk_space_to_start_eviction_; + + // Not owned; quota_eviction_handler owns us. + QuotaEvictionHandler* quota_eviction_handler_; + + Statistics statistics_; + Statistics previous_statistics_; + EvictionRoundStatistics round_statistics_; + base::Time time_of_end_of_last_nonskipped_round_; + base::Time time_of_end_of_last_round_; + + int64 interval_ms_; + bool repeated_eviction_; + + base::OneShotTimer<QuotaTemporaryStorageEvictor> eviction_timer_; + base::RepeatingTimer<QuotaTemporaryStorageEvictor> histogram_timer_; + base::WeakPtrFactory<QuotaTemporaryStorageEvictor> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaTemporaryStorageEvictor); +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_QUOTA_TEMPORARY_STORAGE_EVICTOR_H_ diff --git a/webkit/browser/quota/quota_temporary_storage_evictor_unittest.cc b/webkit/browser/quota/quota_temporary_storage_evictor_unittest.cc new file mode 100644 index 0000000..59acd79 --- /dev/null +++ b/webkit/browser/quota/quota_temporary_storage_evictor_unittest.cc @@ -0,0 +1,410 @@ +// Copyright 2013 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 <list> +#include <map> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/message_loop_proxy.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/quota/mock_storage_client.h" +#include "webkit/browser/quota/quota_manager.h" +#include "webkit/browser/quota/quota_temporary_storage_evictor.h" + +namespace quota { + +class QuotaTemporaryStorageEvictorTest; + +namespace { + +class MockQuotaEvictionHandler : public quota::QuotaEvictionHandler { + public: + explicit MockQuotaEvictionHandler(QuotaTemporaryStorageEvictorTest *test) + : quota_(0), + available_space_(0), + error_on_evict_origin_data_(false), + error_on_get_usage_and_quota_(false) {} + + virtual void EvictOriginData( + const GURL& origin, + StorageType type, + const EvictOriginDataCallback& callback) OVERRIDE { + if (error_on_evict_origin_data_) { + callback.Run(quota::kQuotaErrorInvalidModification); + return; + } + int64 origin_usage = EnsureOriginRemoved(origin); + if (origin_usage >= 0) + available_space_ += origin_usage; + callback.Run(quota::kQuotaStatusOk); + } + + virtual void GetUsageAndQuotaForEviction( + const UsageAndQuotaCallback& callback) OVERRIDE { + if (error_on_get_usage_and_quota_) { + callback.Run(quota::kQuotaErrorInvalidAccess, UsageAndQuota()); + return; + } + if (!task_for_get_usage_and_quota_.is_null()) + task_for_get_usage_and_quota_.Run(); + UsageAndQuota quota_and_usage(-1, GetUsage(), quota_, available_space_); + callback.Run(quota::kQuotaStatusOk, quota_and_usage); + } + + virtual void GetLRUOrigin( + StorageType type, + const GetLRUOriginCallback& callback) OVERRIDE { + if (origin_order_.empty()) + callback.Run(GURL()); + else + callback.Run(GURL(origin_order_.front())); + } + + int64 GetUsage() const { + int64 total_usage = 0; + for (std::map<GURL, int64>::const_iterator p = origins_.begin(); + p != origins_.end(); + ++p) + total_usage += p->second; + return total_usage; + } + + void set_quota(int64 quota) { + quota_ = quota; + } + void set_available_space(int64 available_space) { + available_space_ = available_space; + } + void set_task_for_get_usage_and_quota(const base::Closure& task) { + task_for_get_usage_and_quota_= task; + } + void set_error_on_evict_origin_data(bool error_on_evict_origin_data) { + error_on_evict_origin_data_ = error_on_evict_origin_data; + } + void set_error_on_get_usage_and_quota(bool error_on_get_usage_and_quota) { + error_on_get_usage_and_quota_ = error_on_get_usage_and_quota; + } + + // Simulates an access to |origin|. It reorders the internal LRU list. + // It internally uses AddOrigin(). + void AccessOrigin(const GURL& origin) { + std::map<GURL, int64>::iterator found = origins_.find(origin); + EXPECT_TRUE(origins_.end() != found); + AddOrigin(origin, found->second); + } + + // Simulates adding or overwriting the |origin| to the internal origin set + // with the |usage|. It also adds or moves the |origin| to the end of the + // LRU list. + void AddOrigin(const GURL& origin, int64 usage) { + EnsureOriginRemoved(origin); + origin_order_.push_back(origin); + origins_[origin] = usage; + } + + private: + int64 EnsureOriginRemoved(const GURL& origin) { + int64 origin_usage; + if (origins_.find(origin) == origins_.end()) + return -1; + else + origin_usage = origins_[origin]; + + origins_.erase(origin); + origin_order_.remove(origin); + return origin_usage; + } + + int64 quota_; + int64 available_space_; + std::list<GURL> origin_order_; + std::map<GURL, int64> origins_; + bool error_on_evict_origin_data_; + bool error_on_get_usage_and_quota_; + + base::Closure task_for_get_usage_and_quota_; +}; + +} // anonymous namespace + +class QuotaTemporaryStorageEvictorTest : public testing::Test { + public: + QuotaTemporaryStorageEvictorTest() + : num_get_usage_and_quota_for_eviction_(0), + weak_factory_(this) {} + + virtual void SetUp() { + quota_eviction_handler_.reset(new MockQuotaEvictionHandler(this)); + + // Run multiple evictions in a single RunUntilIdle() when interval_ms == 0 + temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor( + quota_eviction_handler_.get(), 0)); + } + + virtual void TearDown() { + temporary_storage_evictor_.reset(); + quota_eviction_handler_.reset(); + base::MessageLoop::current()->RunUntilIdle(); + } + + void TaskForRepeatedEvictionTest( + const std::pair<GURL, int64>& origin_to_be_added, + const GURL& origin_to_be_accessed, + int expected_usage_after_first, + int expected_usage_after_second) { + EXPECT_GE(4, num_get_usage_and_quota_for_eviction_); + switch (num_get_usage_and_quota_for_eviction_) { + case 2: + EXPECT_EQ(expected_usage_after_first, + quota_eviction_handler()->GetUsage()); + if (!origin_to_be_added.first.is_empty()) + quota_eviction_handler()->AddOrigin(origin_to_be_added.first, + origin_to_be_added.second); + if (!origin_to_be_accessed.is_empty()) + quota_eviction_handler()->AccessOrigin(origin_to_be_accessed); + break; + case 3: + EXPECT_EQ(expected_usage_after_second, + quota_eviction_handler()->GetUsage()); + temporary_storage_evictor()->set_repeated_eviction(false); + break; + } + ++num_get_usage_and_quota_for_eviction_; + } + + protected: + MockQuotaEvictionHandler* quota_eviction_handler() const { + return static_cast<MockQuotaEvictionHandler*>( + quota_eviction_handler_.get()); + } + + QuotaTemporaryStorageEvictor* temporary_storage_evictor() const { + return temporary_storage_evictor_.get(); + } + + const QuotaTemporaryStorageEvictor::Statistics& statistics() const { + return temporary_storage_evictor()->statistics_; + } + + void set_repeated_eviction(bool repeated_eviction) const { + return temporary_storage_evictor_->set_repeated_eviction(repeated_eviction); + } + + int num_get_usage_and_quota_for_eviction() const { + return num_get_usage_and_quota_for_eviction_; + } + + int64 default_min_available_disk_space_to_start_eviction() const { + return 1000 * 1000 * 500; + } + + void set_min_available_disk_space_to_start_eviction(int64 value) const { + temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction( + value); + } + + void reset_min_available_disk_space_to_start_eviction() const { + temporary_storage_evictor_-> + reset_min_available_disk_space_to_start_eviction(); + } + + base::MessageLoop message_loop_; + scoped_ptr<MockQuotaEvictionHandler> quota_eviction_handler_; + scoped_ptr<QuotaTemporaryStorageEvictor> temporary_storage_evictor_; + + int num_get_usage_and_quota_for_eviction_; + + base::WeakPtrFactory<QuotaTemporaryStorageEvictorTest> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(QuotaTemporaryStorageEvictorTest); +}; + +TEST_F(QuotaTemporaryStorageEvictorTest, SimpleEvictionTest) { + quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 3000); + quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 200); + quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 500); + quota_eviction_handler()->set_quota(4000); + quota_eviction_handler()->set_available_space(1000000000); + EXPECT_EQ(3000 + 200 + 500, quota_eviction_handler()->GetUsage()); + set_repeated_eviction(false); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(200 + 500, quota_eviction_handler()->GetUsage()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(1, statistics().num_evicted_origins); + EXPECT_EQ(1, statistics().num_eviction_rounds); + EXPECT_EQ(0, statistics().num_skipped_eviction_rounds); +} + +TEST_F(QuotaTemporaryStorageEvictorTest, MultipleEvictionTest) { + quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 20); + quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 2900); + quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 450); + quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 400); + quota_eviction_handler()->set_quota(4000); + quota_eviction_handler()->set_available_space(1000000000); + EXPECT_EQ(20 + 2900 + 450 + 400, quota_eviction_handler()->GetUsage()); + set_repeated_eviction(false); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(450 + 400, quota_eviction_handler()->GetUsage()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(2, statistics().num_evicted_origins); + EXPECT_EQ(1, statistics().num_eviction_rounds); + EXPECT_EQ(0, statistics().num_skipped_eviction_rounds); +} + +TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionTest) { + const int64 a_size = 400; + const int64 b_size = 150; + const int64 c_size = 120; + const int64 d_size = 292; + const int64 initial_total_size = a_size + b_size + c_size + d_size; + const int64 e_size = 275; + + quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size); + quota_eviction_handler()->set_quota(1000); + quota_eviction_handler()->set_available_space(1000000000); + quota_eviction_handler()->set_task_for_get_usage_and_quota( + base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest, + weak_factory_.GetWeakPtr(), + std::make_pair(GURL("http://www.e.com"), e_size), GURL(), + initial_total_size - d_size, + initial_total_size - d_size + e_size - c_size)); + EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage()); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(initial_total_size - d_size + e_size - c_size - b_size, + quota_eviction_handler()->GetUsage()); + EXPECT_EQ(5, num_get_usage_and_quota_for_eviction()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(3, statistics().num_evicted_origins); + EXPECT_EQ(2, statistics().num_eviction_rounds); + EXPECT_EQ(0, statistics().num_skipped_eviction_rounds); +} + +TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionSkippedTest) { + const int64 a_size = 400; + const int64 b_size = 150; + const int64 c_size = 120; + const int64 d_size = 292; + const int64 initial_total_size = a_size + b_size + c_size + d_size; + + quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size); + quota_eviction_handler()->set_quota(1000); + quota_eviction_handler()->set_available_space(1000000000); + quota_eviction_handler()->set_task_for_get_usage_and_quota( + base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest, + weak_factory_.GetWeakPtr(), std::make_pair(GURL(), 0), GURL(), + initial_total_size - d_size, initial_total_size - d_size)); + EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage()); + set_repeated_eviction(true); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(initial_total_size - d_size, quota_eviction_handler()->GetUsage()); + EXPECT_EQ(4, num_get_usage_and_quota_for_eviction()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(1, statistics().num_evicted_origins); + EXPECT_EQ(3, statistics().num_eviction_rounds); + EXPECT_EQ(2, statistics().num_skipped_eviction_rounds); +} + +TEST_F(QuotaTemporaryStorageEvictorTest, RepeatedEvictionWithAccessOriginTest) { + const int64 a_size = 400; + const int64 b_size = 150; + const int64 c_size = 120; + const int64 d_size = 292; + const int64 initial_total_size = a_size + b_size + c_size + d_size; + const int64 e_size = 275; + + quota_eviction_handler()->AddOrigin(GURL("http://www.d.com"), d_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.c.com"), c_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.b.com"), b_size); + quota_eviction_handler()->AddOrigin(GURL("http://www.a.com"), a_size); + quota_eviction_handler()->set_quota(1000); + quota_eviction_handler()->set_available_space(1000000000); + quota_eviction_handler()->set_task_for_get_usage_and_quota( + base::Bind(&QuotaTemporaryStorageEvictorTest::TaskForRepeatedEvictionTest, + weak_factory_.GetWeakPtr(), + std::make_pair(GURL("http://www.e.com"), e_size), + GURL("http://www.c.com"), + initial_total_size - d_size, + initial_total_size - d_size + e_size - b_size)); + EXPECT_EQ(initial_total_size, quota_eviction_handler()->GetUsage()); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(initial_total_size - d_size + e_size - b_size - a_size, + quota_eviction_handler()->GetUsage()); + EXPECT_EQ(5, num_get_usage_and_quota_for_eviction()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(3, statistics().num_evicted_origins); + EXPECT_EQ(2, statistics().num_eviction_rounds); + EXPECT_EQ(0, statistics().num_skipped_eviction_rounds); +} + +TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceNonEvictionTest) { + quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 414); + quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 450); + quota_eviction_handler()->set_quota(10000); + quota_eviction_handler()->set_available_space( + default_min_available_disk_space_to_start_eviction() - 350); + EXPECT_EQ(414 + 450, quota_eviction_handler()->GetUsage()); + reset_min_available_disk_space_to_start_eviction(); + set_repeated_eviction(false); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(414 + 450, quota_eviction_handler()->GetUsage()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(0, statistics().num_evicted_origins); + EXPECT_EQ(1, statistics().num_eviction_rounds); + EXPECT_EQ(1, statistics().num_skipped_eviction_rounds); +} + +TEST_F(QuotaTemporaryStorageEvictorTest, DiskSpaceEvictionTest) { + quota_eviction_handler()->AddOrigin(GURL("http://www.z.com"), 294); + quota_eviction_handler()->AddOrigin(GURL("http://www.y.com"), 120); + quota_eviction_handler()->AddOrigin(GURL("http://www.x.com"), 150); + quota_eviction_handler()->AddOrigin(GURL("http://www.w.com"), 300); + quota_eviction_handler()->set_quota(10000); + quota_eviction_handler()->set_available_space( + default_min_available_disk_space_to_start_eviction() - 350); + EXPECT_EQ(294 + 120 + 150 + 300, quota_eviction_handler()->GetUsage()); + set_min_available_disk_space_to_start_eviction( + default_min_available_disk_space_to_start_eviction()); + set_repeated_eviction(false); + temporary_storage_evictor()->Start(); + base::MessageLoop::current()->RunUntilIdle(); + EXPECT_EQ(150 + 300, quota_eviction_handler()->GetUsage()); + + EXPECT_EQ(0, statistics().num_errors_on_evicting_origin); + EXPECT_EQ(0, statistics().num_errors_on_getting_usage_and_quota); + EXPECT_EQ(2, statistics().num_evicted_origins); + EXPECT_EQ(1, statistics().num_eviction_rounds); + EXPECT_EQ(0, statistics().num_skipped_eviction_rounds); +} + +} // namespace quota diff --git a/webkit/browser/quota/special_storage_policy.cc b/webkit/browser/quota/special_storage_policy.cc new file mode 100644 index 0000000..9aacb04 --- /dev/null +++ b/webkit/browser/quota/special_storage_policy.cc @@ -0,0 +1,38 @@ +// Copyright 2013 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 "webkit/browser/quota/special_storage_policy.h" + +namespace quota { + +SpecialStoragePolicy::Observer::~Observer() {} + +SpecialStoragePolicy::SpecialStoragePolicy() {} + +SpecialStoragePolicy::~SpecialStoragePolicy() {} + +void SpecialStoragePolicy::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void SpecialStoragePolicy::RemoveObserver(Observer* observer) { + observers_.RemoveObserver(observer); +} + +void SpecialStoragePolicy::NotifyGranted(const GURL& origin, int change_flags) { + scoped_refptr<SpecialStoragePolicy> protect(this); + FOR_EACH_OBSERVER(Observer, observers_, OnGranted(origin, change_flags)); +} + +void SpecialStoragePolicy::NotifyRevoked(const GURL& origin, int change_flags) { + scoped_refptr<SpecialStoragePolicy> protect(this); + FOR_EACH_OBSERVER(Observer, observers_, OnRevoked(origin, change_flags)); +} + +void SpecialStoragePolicy::NotifyCleared() { + scoped_refptr<SpecialStoragePolicy> protect(this); + FOR_EACH_OBSERVER(Observer, observers_, OnCleared()); +} + +} // namespace quota diff --git a/webkit/browser/quota/special_storage_policy.h b/webkit/browser/quota/special_storage_policy.h new file mode 100644 index 0000000..a16fed2 --- /dev/null +++ b/webkit/browser/quota/special_storage_policy.h @@ -0,0 +1,83 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_ +#define WEBKIT_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "webkit/storage/webkit_storage_export.h" + +class GURL; + +namespace quota { + +// Special rights are granted to 'extensions' and 'applications'. The +// storage subsystems query this interface to determine which origins +// have these rights. Chrome provides an impl that is cognizant of what +// is currently installed in the extensions system. +// The IsSomething() methods must be thread-safe, however Observers should +// only be notified, added, and removed on the IO thead. +class WEBKIT_STORAGE_EXPORT SpecialStoragePolicy + : public base::RefCountedThreadSafe<SpecialStoragePolicy> { + public: + typedef int StoragePolicy; + enum ChangeFlags { + STORAGE_PROTECTED = 1 << 0, + STORAGE_UNLIMITED = 1 << 1, + }; + + class WEBKIT_STORAGE_EXPORT Observer { + public: + virtual void OnGranted(const GURL& origin, int change_flags) = 0; + virtual void OnRevoked(const GURL& origin, int change_flags) = 0; + virtual void OnCleared() = 0; + + protected: + virtual ~Observer(); + }; + + SpecialStoragePolicy(); + + // Protected storage is not subject to removal by the browsing data remover. + virtual bool IsStorageProtected(const GURL& origin) = 0; + + // Unlimited storage is not subject to 'quotas'. + virtual bool IsStorageUnlimited(const GURL& origin) = 0; + + // Some origins (e.g. installed apps) have access to the size of the remaining + // disk capacity. + virtual bool CanQueryDiskSize(const GURL& origin) = 0; + + // Checks if extension identified with |extension_id| is registered as + // file handler. + virtual bool IsFileHandler(const std::string& extension_id) = 0; + + // Some origins are only allowed to store session-only data which is deleted + // when the session ends. + virtual bool IsStorageSessionOnly(const GURL& origin) = 0; + + // Returns true if some origins are only allowed session-only storage. + virtual bool HasSessionOnlyOrigins() = 0; + + // Adds/removes an observer, the policy does not take + // ownership of the observer. Should only be called on the IO thread. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + protected: + friend class base::RefCountedThreadSafe<SpecialStoragePolicy>; + virtual ~SpecialStoragePolicy(); + void NotifyGranted(const GURL& origin, int change_flags); + void NotifyRevoked(const GURL& origin, int change_flags); + void NotifyCleared(); + + ObserverList<Observer> observers_; +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_SPECIAL_STORAGE_POLICY_H_ diff --git a/webkit/browser/quota/usage_tracker.cc b/webkit/browser/quota/usage_tracker.cc new file mode 100644 index 0000000..6026d38 --- /dev/null +++ b/webkit/browser/quota/usage_tracker.cc @@ -0,0 +1,597 @@ +// Copyright 2013 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 "webkit/browser/quota/usage_tracker.h" + +#include <algorithm> +#include <deque> +#include <set> +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/message_loop_proxy.h" +#include "base/stl_util.h" +#include "net/base/net_util.h" + +namespace quota { + +namespace { + +typedef ClientUsageTracker::OriginUsageAccumulator OriginUsageAccumulator; +typedef ClientUsageTracker::OriginSetByHost OriginSetByHost; + +void DidGetOriginUsage(const OriginUsageAccumulator& accumulator, + const GURL& origin, + int64 usage) { + accumulator.Run(origin, usage); +} + +void DidGetHostUsage(const UsageCallback& callback, + int64 cached_usage, + int64 non_cached_usage) { + DCHECK_GE(cached_usage, 0); + DCHECK_GE(non_cached_usage, 0); + callback.Run(cached_usage + non_cached_usage); +} + +void NoopHostUsageCallback(int64 usage) {} + +bool EraseOriginFromOriginSet(OriginSetByHost* origins_by_host, + const std::string& host, + const GURL& origin) { + OriginSetByHost::iterator found = origins_by_host->find(host); + if (found == origins_by_host->end()) + return false; + + if (!found->second.erase(origin)) + return false; + + if (found->second.empty()) + origins_by_host->erase(host); + return true; +} + +bool OriginSetContainsOrigin(const OriginSetByHost& origins, + const std::string& host, + const GURL& origin) { + OriginSetByHost::const_iterator itr = origins.find(host); + return itr != origins.end() && ContainsKey(itr->second, origin); +} + +void DidGetGlobalUsageForLimitedGlobalUsage(const UsageCallback& callback, + int64 total_global_usage, + int64 global_unlimited_usage) { + callback.Run(total_global_usage - global_unlimited_usage); +} + +} // namespace + +// UsageTracker ---------------------------------------------------------- + +UsageTracker::UsageTracker(const QuotaClientList& clients, + StorageType type, + SpecialStoragePolicy* special_storage_policy) + : type_(type), + weak_factory_(this) { + for (QuotaClientList::const_iterator iter = clients.begin(); + iter != clients.end(); + ++iter) { + client_tracker_map_[(*iter)->id()] = + new ClientUsageTracker(this, *iter, type, special_storage_policy); + } +} + +UsageTracker::~UsageTracker() { + STLDeleteValues(&client_tracker_map_); +} + +ClientUsageTracker* UsageTracker::GetClientTracker(QuotaClient::ID client_id) { + ClientTrackerMap::iterator found = client_tracker_map_.find(client_id); + if (found != client_tracker_map_.end()) + return found->second; + return NULL; +} + +void UsageTracker::GetGlobalLimitedUsage(const UsageCallback& callback) { + GetGlobalUsage(base::Bind(&DidGetGlobalUsageForLimitedGlobalUsage, callback)); +} + +void UsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) { + if (!global_usage_callbacks_.Add(callback)) + return; + + AccumulateInfo* info = new AccumulateInfo; + // Calling GetGlobalUsage(accumulator) may synchronously + // return if the usage is cached, which may in turn dispatch + // the completion callback before we finish looping over + // all clients (because info->pending_clients may reach 0 + // during the loop). + // To avoid this, we add one more pending client as a sentinel + // and fire the sentinel callback at the end. + info->pending_clients = client_tracker_map_.size() + 1; + GlobalUsageCallback accumulator = base::Bind( + &UsageTracker::AccumulateClientGlobalUsage, weak_factory_.GetWeakPtr(), + base::Owned(info)); + + for (ClientTrackerMap::iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); + ++iter) + iter->second->GetGlobalUsage(accumulator); + + // Fire the sentinel as we've now called GetGlobalUsage for all clients. + accumulator.Run(0, 0); +} + +void UsageTracker::GetHostUsage(const std::string& host, + const UsageCallback& callback) { + if (!host_usage_callbacks_.Add(host, callback)) + return; + + AccumulateInfo* info = new AccumulateInfo; + // Calling GetHostUsage(accumulator) may synchronously + // return if the usage is cached, which may in turn dispatch + // the completion callback before we finish looping over + // all clients (because info->pending_clients may reach 0 + // during the loop). + // To avoid this, we add one more pending client as a sentinel + // and fire the sentinel callback at the end. + info->pending_clients = client_tracker_map_.size() + 1; + UsageCallback accumulator = base::Bind( + &UsageTracker::AccumulateClientHostUsage, weak_factory_.GetWeakPtr(), + base::Owned(info), host); + + for (ClientTrackerMap::iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); + ++iter) + iter->second->GetHostUsage(host, accumulator); + + // Fire the sentinel as we've now called GetHostUsage for all clients. + accumulator.Run(0); +} + +void UsageTracker::UpdateUsageCache( + QuotaClient::ID client_id, const GURL& origin, int64 delta) { + ClientUsageTracker* client_tracker = GetClientTracker(client_id); + DCHECK(client_tracker); + client_tracker->UpdateUsageCache(origin, delta); +} + +void UsageTracker::GetCachedHostsUsage( + std::map<std::string, int64>* host_usage) const { + DCHECK(host_usage); + host_usage->clear(); + for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); ++iter) { + iter->second->GetCachedHostsUsage(host_usage); + } +} + +void UsageTracker::GetCachedOrigins(std::set<GURL>* origins) const { + DCHECK(origins); + origins->clear(); + for (ClientTrackerMap::const_iterator iter = client_tracker_map_.begin(); + iter != client_tracker_map_.end(); ++iter) { + iter->second->GetCachedOrigins(origins); + } +} + +void UsageTracker::SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + bool enabled) { + ClientUsageTracker* client_tracker = GetClientTracker(client_id); + DCHECK(client_tracker); + + client_tracker->SetUsageCacheEnabled(origin, enabled); +} + +void UsageTracker::AccumulateClientGlobalUsage(AccumulateInfo* info, + int64 usage, + int64 unlimited_usage) { + info->usage += usage; + info->unlimited_usage += unlimited_usage; + + if (--info->pending_clients) + return; + + // Defend against confusing inputs from clients. + if (info->usage < 0) + info->usage = 0; + + // TODO(michaeln): The unlimited number is not trustworthy, it + // can get out of whack when apps are installed or uninstalled. + if (info->unlimited_usage > info->usage) + info->unlimited_usage = info->usage; + else if (info->unlimited_usage < 0) + info->unlimited_usage = 0; + + // All the clients have returned their usage data. Dispatch the + // pending callbacks. + global_usage_callbacks_.Run(MakeTuple(info->usage, info->unlimited_usage)); +} + +void UsageTracker::AccumulateClientHostUsage(AccumulateInfo* info, + const std::string& host, + int64 usage) { + info->usage += usage; + if (--info->pending_clients) + return; + + // Defend against confusing inputs from clients. + if (info->usage < 0) + info->usage = 0; + + // All the clients have returned their usage data. Dispatch the + // pending callbacks. + host_usage_callbacks_.Run(host, MakeTuple(info->usage)); +} + +// ClientUsageTracker ---------------------------------------------------- + +ClientUsageTracker::ClientUsageTracker( + UsageTracker* tracker, QuotaClient* client, StorageType type, + SpecialStoragePolicy* special_storage_policy) + : tracker_(tracker), + client_(client), + type_(type), + global_limited_usage_(0), + global_unlimited_usage_(0), + global_usage_retrieved_(false), + special_storage_policy_(special_storage_policy) { + DCHECK(tracker_); + DCHECK(client_); + if (special_storage_policy_) + special_storage_policy_->AddObserver(this); +} + +ClientUsageTracker::~ClientUsageTracker() { + if (special_storage_policy_) + special_storage_policy_->RemoveObserver(this); +} + +void ClientUsageTracker::GetGlobalUsage(const GlobalUsageCallback& callback) { + if (global_usage_retrieved_ && + non_cached_limited_origins_by_host_.empty() && + non_cached_unlimited_origins_by_host_.empty()) { + callback.Run(global_limited_usage_ + global_unlimited_usage_, + global_unlimited_usage_); + return; + } + + client_->GetOriginsForType(type_, base::Bind( + &ClientUsageTracker::DidGetOriginsForGlobalUsage, AsWeakPtr(), + callback)); +} + +void ClientUsageTracker::GetHostUsage( + const std::string& host, const UsageCallback& callback) { + if (ContainsKey(cached_hosts_, host) && + !ContainsKey(non_cached_limited_origins_by_host_, host) && + !ContainsKey(non_cached_unlimited_origins_by_host_, host)) { + // TODO(kinuko): Drop host_usage_map_ cache periodically. + callback.Run(GetCachedHostUsage(host)); + return; + } + + if (!host_usage_accumulators_.Add(host, base::Bind( + &DidGetHostUsage, callback))) + return; + client_->GetOriginsForHost(type_, host, base::Bind( + &ClientUsageTracker::DidGetOriginsForHostUsage, AsWeakPtr(), host)); +} + +void ClientUsageTracker::UpdateUsageCache( + const GURL& origin, int64 delta) { + std::string host = net::GetHostOrSpecFromURL(origin); + if (cached_hosts_.find(host) != cached_hosts_.end()) { + if (!IsUsageCacheEnabledForOrigin(origin)) + return; + + cached_usage_by_host_[host][origin] += delta; + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; + else + global_limited_usage_ += delta; + DCHECK_GE(cached_usage_by_host_[host][origin], 0); + DCHECK_GE(global_limited_usage_, 0); + return; + } + + // We don't know about this host yet, so populate our cache for it. + GetHostUsage(host, base::Bind(&NoopHostUsageCallback)); +} + +void ClientUsageTracker::GetCachedHostsUsage( + std::map<std::string, int64>* host_usage) const { + DCHECK(host_usage); + for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin(); + host_iter != cached_usage_by_host_.end(); host_iter++) { + const std::string& host = host_iter->first; + (*host_usage)[host] += GetCachedHostUsage(host); + } +} + +void ClientUsageTracker::GetCachedOrigins(std::set<GURL>* origins) const { + DCHECK(origins); + for (HostUsageMap::const_iterator host_iter = cached_usage_by_host_.begin(); + host_iter != cached_usage_by_host_.end(); host_iter++) { + const UsageMap& origin_map = host_iter->second; + for (UsageMap::const_iterator origin_iter = origin_map.begin(); + origin_iter != origin_map.end(); origin_iter++) { + origins->insert(origin_iter->first); + } + } +} + +void ClientUsageTracker::SetUsageCacheEnabled(const GURL& origin, + bool enabled) { + std::string host = net::GetHostOrSpecFromURL(origin); + if (!enabled) { + // Erase |origin| from cache and subtract its usage. + HostUsageMap::iterator found_host = cached_usage_by_host_.find(host); + if (found_host != cached_usage_by_host_.end()) { + UsageMap& cached_usage_for_host = found_host->second; + + UsageMap::iterator found = cached_usage_for_host.find(origin); + if (found != cached_usage_for_host.end()) { + int64 usage = found->second; + UpdateUsageCache(origin, -usage); + cached_usage_for_host.erase(found); + if (cached_usage_for_host.empty()) { + cached_usage_by_host_.erase(found_host); + cached_hosts_.erase(host); + } + } + } + + if (IsStorageUnlimited(origin)) + non_cached_unlimited_origins_by_host_[host].insert(origin); + else + non_cached_limited_origins_by_host_[host].insert(origin); + } else { + // Erase |origin| from |non_cached_origins_| and invalidate the usage cache + // for the host. + if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_, + host, origin) || + EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_, + host, origin)) { + cached_hosts_.erase(host); + global_usage_retrieved_ = false; + } + } +} + +void ClientUsageTracker::DidGetOriginsForGlobalUsage( + const GlobalUsageCallback& callback, + const std::set<GURL>& origins) { + OriginSetByHost origins_by_host; + for (std::set<GURL>::const_iterator itr = origins.begin(); + itr != origins.end(); ++itr) + origins_by_host[net::GetHostOrSpecFromURL(*itr)].insert(*itr); + + AccumulateInfo* info = new AccumulateInfo; + // Getting host usage may synchronously return the result if the usage is + // cached, which may in turn dispatch the completion callback before we finish + // looping over all hosts (because info->pending_jobs may reach 0 during the + // loop). To avoid this, we add one more pending host as a sentinel and + // fire the sentinel callback at the end. + info->pending_jobs = origins_by_host.size() + 1; + HostUsageAccumulator accumulator = + base::Bind(&ClientUsageTracker::AccumulateHostUsage, AsWeakPtr(), + base::Owned(info), callback); + + for (OriginSetByHost::iterator itr = origins_by_host.begin(); + itr != origins_by_host.end(); ++itr) { + if (host_usage_accumulators_.Add(itr->first, accumulator)) + GetUsageForOrigins(itr->first, itr->second); + } + + // Fire the sentinel as we've now called GetUsageForOrigins for all clients. + accumulator.Run(0, 0); +} + +void ClientUsageTracker::AccumulateHostUsage( + AccumulateInfo* info, + const GlobalUsageCallback& callback, + int64 cached_usage, + int64 non_cached_usage) { + info->cached_usage += cached_usage; + info->non_cached_usage += non_cached_usage; + if (--info->pending_jobs) + return; + + int64 total_usage = info->cached_usage + info->non_cached_usage; + int64 unlimited_usage = global_unlimited_usage_ + info->non_cached_usage; + + DCHECK_GE(total_usage, 0); + DCHECK_GE(unlimited_usage, 0); + if (unlimited_usage > total_usage) + unlimited_usage = total_usage; + + global_usage_retrieved_ = true; + callback.Run(total_usage, unlimited_usage); +} + +void ClientUsageTracker::DidGetOriginsForHostUsage( + const std::string& host, + const std::set<GURL>& origins) { + GetUsageForOrigins(host, origins); +} + +void ClientUsageTracker::GetUsageForOrigins( + const std::string& host, + const std::set<GURL>& origins) { + AccumulateInfo* info = new AccumulateInfo; + // Getting origin usage may synchronously return the result if the usage is + // cached, which may in turn dispatch the completion callback before we finish + // looping over all origins (because info->pending_jobs may reach 0 during the + // loop). To avoid this, we add one more pending origin as a sentinel and + // fire the sentinel callback at the end. + info->pending_jobs = origins.size() + 1; + OriginUsageAccumulator accumulator = + base::Bind(&ClientUsageTracker::AccumulateOriginUsage, AsWeakPtr(), + base::Owned(info), host); + + for (std::set<GURL>::const_iterator itr = origins.begin(); + itr != origins.end(); ++itr) { + DCHECK_EQ(host, net::GetHostOrSpecFromURL(*itr)); + + int64 origin_usage = 0; + if (GetCachedOriginUsage(*itr, &origin_usage)) { + accumulator.Run(*itr, origin_usage); + } else { + client_->GetOriginUsage(*itr, type_, base::Bind( + &DidGetOriginUsage, accumulator, *itr)); + } + } + + // Fire the sentinel as we've now called GetOriginUsage for all clients. + accumulator.Run(GURL(), 0); +} + +void ClientUsageTracker::AccumulateOriginUsage(AccumulateInfo* info, + const std::string& host, + const GURL& origin, + int64 usage) { + if (!origin.is_empty()) { + if (usage < 0) + usage = 0; + + if (IsUsageCacheEnabledForOrigin(origin)) { + info->cached_usage += usage; + AddCachedOrigin(origin, usage); + } else { + info->non_cached_usage += usage; + } + } + if (--info->pending_jobs) + return; + + AddCachedHost(host); + host_usage_accumulators_.Run( + host, MakeTuple(info->cached_usage, info->non_cached_usage)); +} + +void ClientUsageTracker::AddCachedOrigin( + const GURL& origin, int64 new_usage) { + if (!IsUsageCacheEnabledForOrigin(origin)) + return; + + std::string host = net::GetHostOrSpecFromURL(origin); + int64* usage = &cached_usage_by_host_[host][origin]; + int64 delta = new_usage - *usage; + *usage = new_usage; + if (delta) { + if (IsStorageUnlimited(origin)) + global_unlimited_usage_ += delta; + else + global_limited_usage_ += delta; + } + DCHECK_GE(*usage, 0); + DCHECK_GE(global_limited_usage_, 0); +} + +void ClientUsageTracker::AddCachedHost(const std::string& host) { + cached_hosts_.insert(host); +} + +int64 ClientUsageTracker::GetCachedHostUsage(const std::string& host) const { + HostUsageMap::const_iterator found = cached_usage_by_host_.find(host); + if (found == cached_usage_by_host_.end()) + return 0; + + int64 usage = 0; + const UsageMap& map = found->second; + for (UsageMap::const_iterator iter = map.begin(); + iter != map.end(); ++iter) { + usage += iter->second; + } + return usage; +} + +bool ClientUsageTracker::GetCachedOriginUsage( + const GURL& origin, + int64* usage) const { + std::string host = net::GetHostOrSpecFromURL(origin); + HostUsageMap::const_iterator found_host = cached_usage_by_host_.find(host); + if (found_host == cached_usage_by_host_.end()) + return false; + + UsageMap::const_iterator found = found_host->second.find(origin); + if (found == found_host->second.end()) + return false; + + DCHECK(IsUsageCacheEnabledForOrigin(origin)); + *usage = found->second; + return true; +} + +bool ClientUsageTracker::IsUsageCacheEnabledForOrigin( + const GURL& origin) const { + std::string host = net::GetHostOrSpecFromURL(origin); + return !OriginSetContainsOrigin(non_cached_limited_origins_by_host_, + host, origin) && + !OriginSetContainsOrigin(non_cached_unlimited_origins_by_host_, + host, origin); +} + +void ClientUsageTracker::OnGranted(const GURL& origin, + int change_flags) { + DCHECK(CalledOnValidThread()); + if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) { + int64 usage = 0; + if (GetCachedOriginUsage(origin, &usage)) { + global_unlimited_usage_ += usage; + global_limited_usage_ -= usage; + } + + std::string host = net::GetHostOrSpecFromURL(origin); + if (EraseOriginFromOriginSet(&non_cached_limited_origins_by_host_, + host, origin)) + non_cached_unlimited_origins_by_host_[host].insert(origin); + } +} + +void ClientUsageTracker::OnRevoked(const GURL& origin, + int change_flags) { + DCHECK(CalledOnValidThread()); + if (change_flags & SpecialStoragePolicy::STORAGE_UNLIMITED) { + int64 usage = 0; + if (GetCachedOriginUsage(origin, &usage)) { + global_unlimited_usage_ -= usage; + global_limited_usage_ += usage; + } + + std::string host = net::GetHostOrSpecFromURL(origin); + if (EraseOriginFromOriginSet(&non_cached_unlimited_origins_by_host_, + host, origin)) + non_cached_limited_origins_by_host_[host].insert(origin); + } +} + +void ClientUsageTracker::OnCleared() { + DCHECK(CalledOnValidThread()); + global_limited_usage_ += global_unlimited_usage_; + global_unlimited_usage_ = 0; + + for (OriginSetByHost::const_iterator host_itr = + non_cached_unlimited_origins_by_host_.begin(); + host_itr != non_cached_unlimited_origins_by_host_.end(); + ++host_itr) { + for (std::set<GURL>::const_iterator origin_itr = host_itr->second.begin(); + origin_itr != host_itr->second.end(); + ++origin_itr) + non_cached_limited_origins_by_host_[host_itr->first].insert(*origin_itr); + } + non_cached_unlimited_origins_by_host_.clear(); +} + +bool ClientUsageTracker::IsStorageUnlimited(const GURL& origin) const { + if (type_ == kStorageTypeSyncable) + return false; + return special_storage_policy_.get() && + special_storage_policy_->IsStorageUnlimited(origin); +} + +} // namespace quota diff --git a/webkit/browser/quota/usage_tracker.h b/webkit/browser/quota/usage_tracker.h new file mode 100644 index 0000000..973ceea --- /dev/null +++ b/webkit/browser/quota/usage_tracker.h @@ -0,0 +1,185 @@ +// Copyright 2013 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 WEBKIT_BROWSER_QUOTA_USAGE_TRACKER_H_ +#define WEBKIT_BROWSER_QUOTA_USAGE_TRACKER_H_ + +#include <list> +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "googleurl/src/gurl.h" +#include "webkit/browser/quota/quota_callbacks.h" +#include "webkit/browser/quota/quota_client.h" +#include "webkit/browser/quota/quota_task.h" +#include "webkit/browser/quota/special_storage_policy.h" +#include "webkit/common/quota/quota_types.h" + +namespace quota { + +class ClientUsageTracker; + +// A helper class that gathers and tracks the amount of data stored in +// all quota clients. +// An instance of this class is created per storage type. +class WEBKIT_STORAGE_EXPORT UsageTracker : public QuotaTaskObserver { + public: + UsageTracker(const QuotaClientList& clients, StorageType type, + SpecialStoragePolicy* special_storage_policy); + virtual ~UsageTracker(); + + StorageType type() const { return type_; } + ClientUsageTracker* GetClientTracker(QuotaClient::ID client_id); + + void GetGlobalLimitedUsage(const UsageCallback& callback); + void GetGlobalUsage(const GlobalUsageCallback& callback); + void GetHostUsage(const std::string& host, const UsageCallback& callback); + void UpdateUsageCache(QuotaClient::ID client_id, + const GURL& origin, + int64 delta); + void GetCachedHostsUsage(std::map<std::string, int64>* host_usage) const; + void GetCachedOrigins(std::set<GURL>* origins) const; + bool IsWorking() const { + return global_usage_callbacks_.HasCallbacks() || + host_usage_callbacks_.HasAnyCallbacks(); + } + + void SetUsageCacheEnabled(QuotaClient::ID client_id, + const GURL& origin, + bool enabled); + + private: + struct AccumulateInfo { + AccumulateInfo() : pending_clients(0), usage(0), unlimited_usage(0) {} + int pending_clients; + int64 usage; + int64 unlimited_usage; + }; + + typedef std::map<QuotaClient::ID, ClientUsageTracker*> ClientTrackerMap; + + friend class ClientUsageTracker; + void AccumulateClientGlobalUsage(AccumulateInfo* info, + int64 usage, + int64 unlimited_usage); + void AccumulateClientHostUsage(AccumulateInfo* info, + const std::string& host, + int64 usage); + + const StorageType type_; + ClientTrackerMap client_tracker_map_; + + GlobalUsageCallbackQueue global_usage_callbacks_; + HostUsageCallbackMap host_usage_callbacks_; + + base::WeakPtrFactory<UsageTracker> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(UsageTracker); +}; + +// This class holds per-client usage tracking information and caches per-host +// usage data. An instance of this class is created per client. +class ClientUsageTracker : public SpecialStoragePolicy::Observer, + public base::NonThreadSafe, + public base::SupportsWeakPtr<ClientUsageTracker> { + public: + typedef base::Callback<void(int64 cached_usage, + int64 non_cached_usage)> HostUsageAccumulator; + typedef base::Callback<void(const GURL& origin, + int64 usage)> OriginUsageAccumulator; + typedef std::map<std::string, std::set<GURL> > OriginSetByHost; + + ClientUsageTracker(UsageTracker* tracker, + QuotaClient* client, + StorageType type, + SpecialStoragePolicy* special_storage_policy); + virtual ~ClientUsageTracker(); + + void GetGlobalUsage(const GlobalUsageCallback& callback); + void GetHostUsage(const std::string& host, const UsageCallback& callback); + void UpdateUsageCache(const GURL& origin, int64 delta); + void GetCachedHostsUsage(std::map<std::string, int64>* host_usage) const; + void GetCachedOrigins(std::set<GURL>* origins) const; + int64 GetCachedOriginsUsage(const std::set<GURL>& origins, + std::vector<GURL>* origins_not_in_cache); + bool IsUsageCacheEnabledForOrigin(const GURL& origin) const; + void SetUsageCacheEnabled(const GURL& origin, bool enabled); + + private: + typedef CallbackQueueMap<HostUsageAccumulator, std::string, + Tuple2<int64, int64> > HostUsageAccumulatorMap; + + typedef std::set<std::string> HostSet; + typedef std::map<GURL, int64> UsageMap; + typedef std::map<std::string, UsageMap> HostUsageMap; + + struct AccumulateInfo { + int pending_jobs; + int64 cached_usage; + int64 non_cached_usage; + + AccumulateInfo() : pending_jobs(0), cached_usage(0), non_cached_usage(0) {} + }; + + void DidGetOriginsForGlobalUsage(const GlobalUsageCallback& callback, + const std::set<GURL>& origins); + void AccumulateHostUsage(AccumulateInfo* info, + const GlobalUsageCallback& callback, + int64 cached_usage, + int64 non_cached_usage); + + void DidGetOriginsForHostUsage(const std::string& host, + const std::set<GURL>& origins); + + void GetUsageForOrigins(const std::string& host, + const std::set<GURL>& origins); + void AccumulateOriginUsage(AccumulateInfo* info, + const std::string& host, + const GURL& origin, + int64 usage); + + // Methods used by our GatherUsage tasks, as a task makes progress + // origins and hosts are added incrementally to the cache. + void AddCachedOrigin(const GURL& origin, int64 usage); + void AddCachedHost(const std::string& host); + + int64 GetCachedHostUsage(const std::string& host) const; + int64 GetCachedGlobalUnlimitedUsage(); + bool GetCachedOriginUsage(const GURL& origin, int64* usage) const; + + // SpecialStoragePolicy::Observer overrides + virtual void OnGranted(const GURL& origin, int change_flags) OVERRIDE; + virtual void OnRevoked(const GURL& origin, int change_flags) OVERRIDE; + virtual void OnCleared() OVERRIDE; + + bool IsStorageUnlimited(const GURL& origin) const; + + UsageTracker* tracker_; + QuotaClient* client_; + const StorageType type_; + + int64 global_limited_usage_; + int64 global_unlimited_usage_; + bool global_usage_retrieved_; + HostSet cached_hosts_; + HostUsageMap cached_usage_by_host_; + + OriginSetByHost non_cached_limited_origins_by_host_; + OriginSetByHost non_cached_unlimited_origins_by_host_; + + GlobalUsageCallbackQueue global_usage_callback_; + HostUsageAccumulatorMap host_usage_accumulators_; + + scoped_refptr<SpecialStoragePolicy> special_storage_policy_; + + DISALLOW_COPY_AND_ASSIGN(ClientUsageTracker); +}; + +} // namespace quota + +#endif // WEBKIT_BROWSER_QUOTA_USAGE_TRACKER_H_ diff --git a/webkit/browser/quota/usage_tracker_unittest.cc b/webkit/browser/quota/usage_tracker_unittest.cc new file mode 100644 index 0000000..b897cfc --- /dev/null +++ b/webkit/browser/quota/usage_tracker_unittest.cc @@ -0,0 +1,277 @@ +// Copyright 2013 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 "base/message_loop.h" +#include "net/base/net_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/browser/quota/mock_special_storage_policy.h" +#include "webkit/browser/quota/usage_tracker.h" + +namespace quota { + +namespace { + +void DidGetGlobalUsage(bool* done, + int64* usage_out, + int64* unlimited_usage_out, + int64 usage, + int64 unlimited_usage) { + EXPECT_FALSE(*done); + *done = true; + *usage_out = usage; + *unlimited_usage_out = unlimited_usage; +} + +void DidGetUsage(bool* done, + int64* usage_out, + int64 usage) { + EXPECT_FALSE(*done); + *done = true; + *usage_out = usage; +} + +} // namespace + +class MockQuotaClient : public QuotaClient { + public: + MockQuotaClient() {} + virtual ~MockQuotaClient() {} + + virtual ID id() const OVERRIDE { + return kFileSystem; + } + + virtual void OnQuotaManagerDestroyed() OVERRIDE {} + + virtual void GetOriginUsage(const GURL& origin, + StorageType type, + const GetUsageCallback& callback) OVERRIDE { + EXPECT_EQ(kStorageTypeTemporary, type); + int64 usage = GetUsage(origin); + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, usage)); + } + + virtual void GetOriginsForType(StorageType type, + const GetOriginsCallback& callback) OVERRIDE { + EXPECT_EQ(kStorageTypeTemporary, type); + std::set<GURL> origins; + for (UsageMap::const_iterator itr = usage_map_.begin(); + itr != usage_map_.end(); ++itr) { + origins.insert(itr->first); + } + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, origins)); + } + + virtual void GetOriginsForHost(StorageType type, + const std::string& host, + const GetOriginsCallback& callback) OVERRIDE { + EXPECT_EQ(kStorageTypeTemporary, type); + std::set<GURL> origins; + for (UsageMap::const_iterator itr = usage_map_.begin(); + itr != usage_map_.end(); ++itr) { + if (net::GetHostOrSpecFromURL(itr->first) == host) + origins.insert(itr->first); + } + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, origins)); + } + + virtual void DeleteOriginData(const GURL& origin, + StorageType type, + const DeletionCallback& callback) OVERRIDE { + EXPECT_EQ(kStorageTypeTemporary, type); + usage_map_.erase(origin); + base::MessageLoop::current()->PostTask( + FROM_HERE, base::Bind(callback, kQuotaStatusOk)); + } + + int64 GetUsage(const GURL& origin) { + UsageMap::const_iterator found = usage_map_.find(origin); + if (found == usage_map_.end()) + return 0; + return found->second; + } + + void SetUsage(const GURL& origin, int64 usage) { + usage_map_[origin] = usage; + } + + int64 UpdateUsage(const GURL& origin, int64 delta) { + return usage_map_[origin] += delta; + } + + private: + typedef std::map<GURL, int64> UsageMap; + + UsageMap usage_map_; + + DISALLOW_COPY_AND_ASSIGN(MockQuotaClient); +}; + +class UsageTrackerTest : public testing::Test { + public: + UsageTrackerTest() + : storage_policy_(new MockSpecialStoragePolicy()), + usage_tracker_(GetUsageTrackerList(), kStorageTypeTemporary, + storage_policy_.get()) { + } + + virtual ~UsageTrackerTest() {} + + UsageTracker* usage_tracker() { + return &usage_tracker_; + } + + void UpdateUsage(const GURL& origin, int64 delta) { + quota_client_.UpdateUsage(origin, delta); + usage_tracker_.UpdateUsageCache(quota_client_.id(), origin, delta); + message_loop_.RunUntilIdle(); + } + + void UpdateUsageWithoutNotification(const GURL& origin, int64 delta) { + quota_client_.UpdateUsage(origin, delta); + } + + void GetGlobalUsage(int64* usage, int64* unlimited_usage) { + bool done = false; + usage_tracker_.GetGlobalUsage(base::Bind( + &DidGetGlobalUsage, + &done, usage, unlimited_usage)); + message_loop_.RunUntilIdle(); + + EXPECT_TRUE(done); + } + + void GetHostUsage(const std::string& host, int64* usage) { + bool done = false; + usage_tracker_.GetHostUsage(host, base::Bind(&DidGetUsage, &done, usage)); + message_loop_.RunUntilIdle(); + + EXPECT_TRUE(done); + } + + void GrantUnlimitedStoragePolicy(const GURL& origin) { + if (!storage_policy_->IsStorageUnlimited(origin)) { + storage_policy_->AddUnlimited(origin); + storage_policy_->NotifyGranted( + origin, SpecialStoragePolicy::STORAGE_UNLIMITED); + } + } + + void RevokeUnlimitedStoragePolicy(const GURL& origin) { + if (storage_policy_->IsStorageUnlimited(origin)) { + storage_policy_->RemoveUnlimited(origin); + storage_policy_->NotifyRevoked( + origin, SpecialStoragePolicy::STORAGE_UNLIMITED); + } + } + + void SetUsageCacheEnabled(const GURL& origin, bool enabled) { + usage_tracker_.SetUsageCacheEnabled( + quota_client_.id(), origin, enabled); + } + + private: + QuotaClientList GetUsageTrackerList() { + QuotaClientList client_list; + client_list.push_back("a_client_); + return client_list; + } + + base::MessageLoop message_loop_; + + scoped_refptr<MockSpecialStoragePolicy> storage_policy_; + MockQuotaClient quota_client_; + UsageTracker usage_tracker_; + + DISALLOW_COPY_AND_ASSIGN(UsageTrackerTest); +}; + +TEST_F(UsageTrackerTest, GrantAndRevokeUnlimitedStorage) { + int64 usage = 0; + int64 unlimited_usage = 0; + int64 host_usage = 0; + GetGlobalUsage(&usage, &unlimited_usage); + EXPECT_EQ(0, usage); + EXPECT_EQ(0, unlimited_usage); + + const GURL origin("http://example.com"); + const std::string host(net::GetHostOrSpecFromURL(origin)); + + UpdateUsage(origin, 100); + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(100, usage); + EXPECT_EQ(0, unlimited_usage); + EXPECT_EQ(100, host_usage); + + GrantUnlimitedStoragePolicy(origin); + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(100, usage); + EXPECT_EQ(100, unlimited_usage); + EXPECT_EQ(100, host_usage); + + RevokeUnlimitedStoragePolicy(origin); + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(100, usage); + EXPECT_EQ(0, unlimited_usage); + EXPECT_EQ(100, host_usage); +} + +TEST_F(UsageTrackerTest, CacheDisabledClientTest) { + int64 usage = 0; + int64 unlimited_usage = 0; + int64 host_usage = 0; + + const GURL origin("http://example.com"); + const std::string host(net::GetHostOrSpecFromURL(origin)); + + UpdateUsage(origin, 100); + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(100, usage); + EXPECT_EQ(0, unlimited_usage); + EXPECT_EQ(100, host_usage); + + UpdateUsageWithoutNotification(origin, 100); + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(100, usage); + EXPECT_EQ(0, unlimited_usage); + EXPECT_EQ(100, host_usage); + + GrantUnlimitedStoragePolicy(origin); + UpdateUsageWithoutNotification(origin, 100); + SetUsageCacheEnabled(origin, false); + UpdateUsageWithoutNotification(origin, 100); + + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(400, usage); + EXPECT_EQ(400, unlimited_usage); + EXPECT_EQ(400, host_usage); + + RevokeUnlimitedStoragePolicy(origin); + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(400, usage); + EXPECT_EQ(400, unlimited_usage); + EXPECT_EQ(400, host_usage); + + SetUsageCacheEnabled(origin, true); + UpdateUsage(origin, 100); + + GetGlobalUsage(&usage, &unlimited_usage); + GetHostUsage(host, &host_usage); + EXPECT_EQ(500, usage); + EXPECT_EQ(0, unlimited_usage); + EXPECT_EQ(500, host_usage); +} + +} // namespace quota diff --git a/webkit/browser/quota/webkit_browser_quota.gypi b/webkit/browser/quota/webkit_browser_quota.gypi new file mode 100644 index 0000000..a277afd --- /dev/null +++ b/webkit/browser/quota/webkit_browser_quota.gypi @@ -0,0 +1,24 @@ +# Copyright 2013 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. + +{ + 'variables': { + 'webkit_browser_quota_sources': [ + '../browser/quota/quota_callbacks.h', + '../browser/quota/quota_client.h', + '../browser/quota/quota_database.cc', + '../browser/quota/quota_database.h', + '../browser/quota/quota_manager.cc', + '../browser/quota/quota_manager.h', + '../browser/quota/quota_task.cc', + '../browser/quota/quota_task.h', + '../browser/quota/quota_temporary_storage_evictor.cc', + '../browser/quota/quota_temporary_storage_evictor.h', + '../browser/quota/special_storage_policy.cc', + '../browser/quota/special_storage_policy.h', + '../browser/quota/usage_tracker.cc', + '../browser/quota/usage_tracker.h', + ], + }, +} diff --git a/webkit/browser/webkit_browser.gypi b/webkit/browser/webkit_browser.gypi index 3fe985d..94a4b9e 100644 --- a/webkit/browser/webkit_browser.gypi +++ b/webkit/browser/webkit_browser.gypi @@ -7,6 +7,7 @@ '../browser/blob/webkit_browser_blob.gypi', '../browser/database/webkit_browser_database.gypi', '../browser/fileapi/webkit_browser_fileapi.gypi', + '../browser/quota/webkit_browser_quota.gypi', ], # TODO(kinuko): Have webkit_browser target and deprecate old gypis like # webkit_storage.gypi. @@ -15,6 +16,7 @@ '<@(webkit_browser_blob_sources)', '<@(webkit_browser_database_sources)', '<@(webkit_browser_fileapi_sources)', + '<@(webkit_browser_quota_sources)', ], }, } |