blob: 24d076bcaeaa78e45cb14e9d77bec3349432504b [file] [log] [blame]
// 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