| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "components/services/storage/public/cpp/constants.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "storage/browser/quota/quota_features.h" |
| #include "storage/browser/quota/quota_manager_impl.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| class QuotaBrowserTest : public ContentBrowserTest { |
| public: |
| QuotaBrowserTest() { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| } |
| |
| base::FilePath profile_path() { |
| return shell() |
| ->web_contents() |
| ->GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetPath(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // TODO(crbug.com/40488499): Android does not support PRE_ tests. |
| #if !BUILDFLAG(IS_ANDROID) |
| IN_PROC_BROWSER_TEST_F(QuotaBrowserTest, PRE_QuotaDatabaseBootstrapTest) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL empty_url(embedded_test_server()->GetURL("/empty.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), empty_url)); |
| |
| // Call storage APIs to populate data on-disk in their legacy file paths. |
| // |
| // Cache Storage |
| EXPECT_EQ(true, EvalJs(shell(), R"( |
| new Promise((resolve) => { |
| self.caches.open("attachements") |
| .then((cache) => { |
| return cache.put("notes.txt", new Response("foo")); |
| }) |
| .then(() => { resolve(true); }) |
| .catch(() => { resolve(false); }); |
| });)")); |
| |
| // IndexedDB |
| EXPECT_EQ(true, EvalJs(shell(), R"( |
| new Promise((resolve) => { |
| const request = window.indexedDB.open('notes'); |
| request.onupgradeneeded = (event) => { |
| event.target.result.createObjectStore('primary', {keyPath: 'id'}); |
| event.target.transaction.commit(); |
| }; |
| request.onsuccess = () => resolve(true); |
| request.onerror = () => resolve(false); |
| });)")); |
| |
| // FileSystem |
| EXPECT_EQ(true, EvalJs(shell(), R"( |
| new Promise((resolve, reject) => { |
| window.webkitRequestFileSystem( |
| window.TEMPORARY, |
| 1024 * 1024, |
| (fs) => { |
| fs.root.getFile('file.txt', {create: true}, (entry) => { |
| entry.createWriter((writer) => { |
| writer.onwriteend = () => { |
| resolve(true); |
| }; |
| writer.onerror = reject; |
| var blob = new Blob(['foo'], {type: 'text/plain'}); |
| writer.write(blob); |
| }, reject); |
| }, reject); |
| }, reject); |
| });)")); |
| |
| // ServiceWorker |
| ASSERT_TRUE(NavigateToURL(shell(), |
| embedded_test_server()->GetURL( |
| "/service_worker/create_service_worker.html"))); |
| EXPECT_EQ("DONE", EvalJs(shell(), "register('empty.js');")); |
| |
| // Verify that the WebStorage directory and QuotaDatabase exists as a result |
| // of accessing Storage APIs. |
| base::FilePath web_storage_dir_path = |
| profile_path().Append(storage::kWebStorageDirectory); |
| EXPECT_TRUE(base::PathExists(web_storage_dir_path)); |
| base::FilePath db_path = web_storage_dir_path.AppendASCII( |
| storage::QuotaManagerImpl::kDatabaseName); |
| EXPECT_TRUE(base::PathExists(db_path)); |
| } |
| |
| // Continue testing after a restart to ensure that processes to the |
| // QuotaDatabase are disconnected. |
| IN_PROC_BROWSER_TEST_F(QuotaBrowserTest, QuotaDatabaseBootstrapTest) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| // Verify that calling the APIs have created files on-disk in their legacy |
| // file paths. This is done after shutdown to ensure data has been flushed to |
| // disk after javascript execution. |
| // |
| // CacheStorage |
| base::FilePath service_worker_dir = |
| profile_path().Append(storage::kServiceWorkerDirectory); |
| EXPECT_TRUE(base::PathExists(service_worker_dir)); |
| base::FilePath cache_dir = |
| service_worker_dir.Append(storage::kCacheStorageDirectory); |
| EXPECT_TRUE(base::PathExists(cache_dir)); |
| EXPECT_FALSE(base::IsDirectoryEmpty(cache_dir)); |
| |
| // IndexedDB |
| base::FilePath idb_dir = profile_path().Append(storage::kIndexedDbDirectory); |
| EXPECT_TRUE(base::PathExists(idb_dir)); |
| EXPECT_FALSE(base::IsDirectoryEmpty(idb_dir)); |
| |
| // FileSystem |
| base::FilePath fs_dir = |
| profile_path().Append(FILE_PATH_LITERAL("File System")); |
| EXPECT_TRUE(base::PathExists(fs_dir)); |
| EXPECT_FALSE(base::IsDirectoryEmpty(fs_dir)); |
| |
| // ServiceWorker |
| base::FilePath script_dir = |
| service_worker_dir.Append(storage::kScriptCacheDirectory); |
| EXPECT_TRUE(base::PathExists(script_dir)); |
| EXPECT_FALSE(base::IsDirectoryEmpty(script_dir)); |
| |
| // Delete WebStorage directory to force a new database creation if one exists |
| // so it triggers the bootstrap task. This is done after shutdown to ensure |
| // files aren't accessed. |
| base::FilePath web_storage_dir_path = |
| profile_path().Append(storage::kWebStorageDirectory); |
| EXPECT_TRUE(base::DeletePathRecursively(web_storage_dir_path)); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL empty_url(embedded_test_server()->GetURL("/empty.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), empty_url)); |
| |
| // Use an API that will cause the Quota subsystem to bootstrap itself. We are |
| // testing that calling this function doesn't hang. |
| EXPECT_EQ(true, EvalJs(shell(), R"( |
| navigator.storage.estimate().then( |
| ()=>{ return true; }, |
| ()=>{ return false; });)")); |
| |
| // Verify that the WebStorage/QuotaManager directory was created as a result |
| // of the Javascript execution. |
| EXPECT_TRUE(base::PathExists(web_storage_dir_path)); |
| base::FilePath db_path = web_storage_dir_path.AppendASCII( |
| storage::QuotaManagerImpl::kDatabaseName); |
| EXPECT_TRUE(base::PathExists(db_path)); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Test for https://crbug.com/1370035 - when a CacheStorage index file without |
| // bucket information is present on disk and the QuotaDatabase has't been |
| // bootstrapped yet, the `CacheStorageManager::GetStorageKeys()` implementation |
| // must not attempt to use the QuotaManagerProxy to lookup bucket information. |
| // Doing so creates a deadlock, because `GetStorageKeys()` would wait for the |
| // bucket information to be returned and the QuotaManager won't respond with |
| // bucket information until the `GetStorageKeys()` call finishes (as part of the |
| // bootstrapping process). |
| IN_PROC_BROWSER_TEST_F(QuotaBrowserTest, |
| QuotaDatabaseBootstrapUnmigratedCacheStorageTest) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| // Set up the profile directory to have a CacheStorage index file that hasn't |
| // been migrated to contain bucket information yet. |
| base::FilePath service_worker_dir_path = |
| profile_path().Append(storage::kServiceWorkerDirectory); |
| base::FilePath cache_storage_dir_path = |
| service_worker_dir_path.Append(storage::kCacheStorageDirectory); |
| |
| EXPECT_FALSE(base::PathExists(service_worker_dir_path)); |
| EXPECT_TRUE(base::CreateDirectory(service_worker_dir_path)); |
| |
| EXPECT_FALSE(base::PathExists(cache_storage_dir_path)); |
| EXPECT_TRUE(base::CreateDirectory(cache_storage_dir_path)); |
| |
| base::FilePath test_cache_storage_origin_path = |
| GetTestFilePath("cache_storage", "storage_key") |
| .AppendASCII("0430f1a484a0ea6d8de562488c5fbeec0111d16f"); |
| |
| EXPECT_TRUE(base::PathExists(test_cache_storage_origin_path)); |
| EXPECT_TRUE(base::CopyDirectory(test_cache_storage_origin_path, |
| cache_storage_dir_path, |
| /*recursive=*/true)); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL empty_url(embedded_test_server()->GetURL("/empty.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), empty_url)); |
| |
| // Assume that the WebStorage directory doesn't exist yet. This indicates that |
| // the QuotaDatabase hasn't been bootstrapped, which is a precondition for |
| // this test. |
| base::FilePath web_storage_dir_path = |
| profile_path().Append(storage::kWebStorageDirectory); |
| EXPECT_FALSE(base::PathExists(web_storage_dir_path)); |
| |
| // Use an API that will cause the Quota subsystem to bootstrap itself. We are |
| // testing that calling this function doesn't hang. |
| EXPECT_EQ(true, EvalJs(shell(), R"( |
| navigator.storage.estimate().then( |
| ()=>{ return true; }, |
| ()=>{ return false; });)")); |
| |
| // Verify that the WebStorage/QuotaManager directory was created as a result |
| // of the Javascript execution. |
| EXPECT_TRUE(base::PathExists(web_storage_dir_path.AppendASCII( |
| storage::QuotaManagerImpl::kDatabaseName))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(QuotaBrowserTest, StorageEstimateWithStaticQuota) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL empty_url(embedded_test_server()->GetURL("/empty.html")); |
| ASSERT_TRUE(NavigateToURL(shell(), empty_url)); |
| |
| // The `navigator.storage.estimate()` API returns an estimate of the amount of |
| // storage available to the website. This value is calculated by the quota |
| // system. This test verifies that the returned value is the expected one when |
| // using the static quota system. The expected value is usage + min(10GiB, |
| // disk rounded up to the nearest 1 GiB). The test verifies that the returned |
| // quota value is <= 10GiB and is rounded. |
| int64_t quota = EvalJs(shell(), R"( |
| navigator.storage.estimate().then( |
| (result)=> { |
| return result.quota; |
| }, |
| ()=>{ return -1; });)") |
| .ExtractDouble(); |
| const int64_t kGBytes = 1024 * 1024 * 1024; |
| EXPECT_LE(quota, 10 * kGBytes); |
| EXPECT_EQ(0, quota % kGBytes); |
| } |
| |
| } // namespace content |