blob: 519820c324c9f933881e383c53315b9aace4a365 [file] [log] [blame]
// Copyright (c) 2012 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 <stddef.h>
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/test/thread_test_helper.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/mock_browsertest_indexed_db_class_factory.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.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 "net/base/escape.h"
#include "net/base/net_errors.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/database/database_util.h"
#include "storage/browser/quota/quota_manager.h"
#include "url/gurl.h"
#include "url/origin.h"
using base::ASCIIToUTF16;
using storage::QuotaManager;
using storage::DatabaseUtil;
using url::Origin;
namespace content {
namespace {
const Origin kFileOrigin = Origin::Create(GURL("file:///"));
}
// This browser test is aimed towards exercising the IndexedDB bindings and
// the actual implementation that lives in the browser side.
class IndexedDBBrowserTest : public ContentBrowserTest,
public ::testing::WithParamInterface<const char*> {
public:
IndexedDBBrowserTest() = default;
void SetUp() override {
GetTestClassFactory()->Reset();
IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(GetIDBClassFactory);
ContentBrowserTest::SetUp();
}
void TearDown() override {
IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(nullptr);
ContentBrowserTest::TearDown();
}
void FailOperation(FailClass failure_class,
FailMethod failure_method,
int fail_on_instance_num,
int fail_on_call_num) {
GetTestClassFactory()->FailOperation(
failure_class, failure_method, fail_on_instance_num, fail_on_call_num);
}
void SimpleTest(const GURL& test_url, bool incognito = false) {
// The test page will perform tests on IndexedDB, then navigate to either
// a #pass or #fail ref.
Shell* the_browser = incognito ? CreateOffTheRecordBrowser() : shell();
VLOG(0) << "Navigating to URL and blocking.";
NavigateToURLBlockUntilNavigationsComplete(the_browser, test_url, 2);
VLOG(0) << "Navigation done.";
std::string result =
the_browser->web_contents()->GetLastCommittedURL().ref();
if (result != "pass") {
std::string js_result;
ASSERT_TRUE(ExecuteScriptAndExtractString(
the_browser, "window.domAutomationController.send(getLog())",
&js_result));
FAIL() << "Failed: " << js_result;
}
}
void NavigateAndWaitForTitle(Shell* shell,
const char* filename,
const char* hash,
const char* expected_string) {
GURL url = GetTestUrl("indexeddb", filename);
if (hash)
url = GURL(url.spec() + hash);
base::string16 expected_title16(ASCIIToUTF16(expected_string));
TitleWatcher title_watcher(shell->web_contents(), expected_title16);
NavigateToURL(shell, url);
EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
}
IndexedDBContextImpl* GetContext(Shell* browser = nullptr) {
if (!browser)
browser = shell();
StoragePartition* partition = BrowserContext::GetDefaultStoragePartition(
browser->web_contents()->GetBrowserContext());
return static_cast<IndexedDBContextImpl*>(partition->GetIndexedDBContext());
}
void SetQuota(int per_host_quota_kilobytes) {
SetTempQuota(per_host_quota_kilobytes,
BrowserContext::GetDefaultStoragePartition(
shell()->web_contents()->GetBrowserContext())
->GetQuotaManager());
}
static void SetTempQuota(int per_host_quota_kilobytes,
scoped_refptr<QuotaManager> qm) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&IndexedDBBrowserTest::SetTempQuota,
per_host_quota_kilobytes, qm));
return;
}
DCHECK_CURRENTLY_ON(BrowserThread::IO);
const int KB = 1024;
qm->SetQuotaSettings(
storage::GetHardCodedSettings(per_host_quota_kilobytes * KB));
}
void DeleteForOrigin(const Origin& origin, Shell* browser = nullptr) {
base::RunLoop loop;
IndexedDBContextImpl* context = GetContext();
context->TaskRunner()->PostTask(FROM_HERE,
base::BindLambdaForTesting([&]() {
context->DeleteForOrigin(kFileOrigin);
loop.Quit();
}));
loop.Run();
}
int64_t RequestUsage(const Origin& origin, Shell* browser = nullptr) {
base::RunLoop loop;
int64_t size;
IndexedDBContextImpl* context = GetContext(browser);
context->TaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
size = context->GetOriginDiskUsage(origin);
loop.Quit();
}));
loop.Run();
return size;
}
int RequestBlobFileCount(const Origin& origin) {
base::RunLoop loop;
int count;
IndexedDBContextImpl* context = GetContext();
context->TaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
count = context->GetOriginBlobFileCount(origin);
loop.Quit();
}));
loop.Run();
return count;
}
bool RequestSchemaDowngrade(const Origin& origin) {
base::RunLoop loop;
bool downgraded;
IndexedDBContextImpl* context = GetContext();
context->TaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
downgraded = context->ForceSchemaDowngrade(origin);
loop.Quit();
}));
loop.Run();
return downgraded;
}
V2SchemaCorruptionStatus RequestHasV2SchemaCorruption(Origin origin) {
base::RunLoop loop;
V2SchemaCorruptionStatus status;
IndexedDBContextImpl* context = GetContext();
context->TaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
status = context->HasV2SchemaCorruption(origin);
loop.Quit();
}));
loop.Run();
return status;
}
protected:
static MockBrowserTestIndexedDBClassFactory* GetTestClassFactory() {
static ::base::LazyInstance<MockBrowserTestIndexedDBClassFactory>::Leaky
s_factory = LAZY_INSTANCE_INITIALIZER;
return s_factory.Pointer();
}
static IndexedDBClassFactory* GetIDBClassFactory() {
return GetTestClassFactory();
}
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTest);
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTest) {
SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorTestIncognito) {
SimpleTest(GetTestUrl("indexeddb", "cursor_test.html"),
true /* incognito */);
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CursorPrefetch) {
SimpleTest(GetTestUrl("indexeddb", "cursor_prefetch.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, IndexTest) {
SimpleTest(GetTestUrl("indexeddb", "index_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyPathTest) {
SimpleTest(GetTestUrl("indexeddb", "key_path_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionGetTest) {
SimpleTest(GetTestUrl("indexeddb", "transaction_get_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, KeyTypesTest) {
SimpleTest(GetTestUrl("indexeddb", "key_types_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ObjectStoreTest) {
SimpleTest(GetTestUrl("indexeddb", "object_store_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DatabaseTest) {
SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, TransactionTest) {
SimpleTest(GetTestUrl("indexeddb", "transaction_test.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CallbackAccounting) {
SimpleTest(GetTestUrl("indexeddb", "callback_accounting.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, GetAllMaxMessageSize) {
SimpleTest(GetTestUrl("indexeddb", "getall_max_message_size.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DoesntHangTest) {
SimpleTest(GetTestUrl("indexeddb", "transaction_run_forever.html"));
CrashTab(shell()->web_contents());
SimpleTest(GetTestUrl("indexeddb", "transaction_not_blocked.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug84933Test) {
const GURL url = GetTestUrl("indexeddb", "bug_84933.html");
// Just navigate to the URL. Test will crash if it fails.
NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug106883Test) {
const GURL url = GetTestUrl("indexeddb", "bug_106883.html");
// Just navigate to the URL. Test will crash if it fails.
NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, Bug109187Test) {
const GURL url = GetTestUrl("indexeddb", "bug_109187.html");
// Just navigate to the URL. Test will crash if it fails.
NavigateToURLBlockUntilNavigationsComplete(shell(), url, 1);
}
class IndexedDBBrowserTestWithLowQuota : public IndexedDBBrowserTest {
public:
IndexedDBBrowserTestWithLowQuota() {}
void SetUpOnMainThread() override {
const int kInitialQuotaKilobytes = 5000;
SetQuota(kInitialQuotaKilobytes);
}
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithLowQuota);
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithLowQuota, QuotaTest) {
SimpleTest(GetTestUrl("indexeddb", "quota_test.html"));
}
class IndexedDBBrowserTestWithGCExposed : public IndexedDBBrowserTest {
public:
IndexedDBBrowserTestWithGCExposed() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc");
}
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithGCExposed);
};
class IndexedDBBrowserTestWithExperimentalWebFeatures
: public IndexedDBBrowserTest {
public:
IndexedDBBrowserTestWithExperimentalWebFeatures() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithExperimentalWebFeatures);
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed,
DatabaseCallbacksTest) {
SimpleTest(GetTestUrl("indexeddb", "database_callbacks_first.html"));
}
static void CopyLevelDBToProfile(Shell* shell,
scoped_refptr<IndexedDBContextImpl> context,
const std::string& test_directory) {
DCHECK(context->TaskRunner()->RunsTasksInCurrentSequence());
base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb"));
base::FilePath test_data_dir =
GetTestFilePath("indexeddb", test_directory.c_str()).Append(leveldb_dir);
base::FilePath dest = context->data_path().Append(leveldb_dir);
// If we don't create the destination directory first, the contents of the
// leveldb directory are copied directly into profile/IndexedDB instead of
// profile/IndexedDB/file__0.xxx/
ASSERT_TRUE(base::CreateDirectory(dest));
const bool kRecursive = true;
ASSERT_TRUE(base::CopyDirectory(test_data_dir,
context->data_path(),
kRecursive));
}
class IndexedDBBrowserTestWithPreexistingLevelDB : public IndexedDBBrowserTest {
public:
IndexedDBBrowserTestWithPreexistingLevelDB() {}
void SetUpOnMainThread() override {
scoped_refptr<IndexedDBContextImpl> context = GetContext();
context->TaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&CopyLevelDBToProfile, shell(), context,
EnclosingLevelDBDir()));
scoped_refptr<base::ThreadTestHelper> helper(
new base::ThreadTestHelper(GetContext()->TaskRunner()));
ASSERT_TRUE(helper->Run());
}
virtual std::string EnclosingLevelDBDir() = 0;
private:
DISALLOW_COPY_AND_ASSIGN(IndexedDBBrowserTestWithPreexistingLevelDB);
};
class IndexedDBBrowserTestWithVersion0Schema : public
IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "migration_from_0"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion0Schema, MigrationTest) {
SimpleTest(GetTestUrl("indexeddb", "migration_test.html"));
}
class IndexedDBBrowserTestWithVersion123456Schema : public
IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "schema_version_123456"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion123456Schema,
DestroyTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
class IndexedDBBrowserTestWithVersion987654SSVData : public
IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "ssv_version_987654"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithVersion987654SSVData,
DestroyTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
class IndexedDBBrowserTestWithCorruptLevelDB : public
IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "corrupt_leveldb"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCorruptLevelDB,
DestroyTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "open_bad_db.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
class IndexedDBBrowserTestWithMissingSSTFile : public
IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "missing_sst"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithMissingSSTFile,
DestroyTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "open_missing_table.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
// IndexedDBBrowserTestWithCrbug899446* capture IDB instances from Chrome stable
// to verify that the current code can read those instances. For more info on
// a case when Chrome canary couldn't read stable's IDB instances, see
// https://crbug.com/899446.
class IndexedDBBrowserTestWithCrbug899446
: public IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "crbug899446"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCrbug899446, StableTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "crbug899446.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
class IndexedDBBrowserTestWithCrbug899446Noai
: public IndexedDBBrowserTestWithPreexistingLevelDB {
std::string EnclosingLevelDBDir() override { return "crbug899446_noai"; }
};
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithCrbug899446Noai, StableTest) {
int64_t original_size = RequestUsage(kFileOrigin);
EXPECT_GT(original_size, 0);
SimpleTest(GetTestUrl("indexeddb", "crbug899446_noai.html"));
int64_t new_size = RequestUsage(kFileOrigin);
EXPECT_GT(new_size, 0);
EXPECT_NE(original_size, new_size);
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, LevelDBLogFileTest) {
// Any page that opens an IndexedDB will work here.
SimpleTest(GetTestUrl("indexeddb", "database_test.html"));
base::FilePath leveldb_dir(FILE_PATH_LITERAL("file__0.indexeddb.leveldb"));
base::FilePath log_file(FILE_PATH_LITERAL("LOG"));
base::FilePath log_file_path =
GetContext()->data_path().Append(leveldb_dir).Append(log_file);
{
base::ScopedAllowBlockingForTesting allow_blocking;
int64_t size;
EXPECT_TRUE(base::GetFileSize(log_file_path, &size));
EXPECT_GT(size, 0);
}
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, CanDeleteWhenOverQuotaTest) {
SimpleTest(GetTestUrl("indexeddb", "fill_up_5k.html"));
int64_t size = RequestUsage(kFileOrigin);
const int kQuotaKilobytes = 2;
EXPECT_GT(size, kQuotaKilobytes * 1024);
SetQuota(kQuotaKilobytes);
SimpleTest(GetTestUrl("indexeddb", "delete_over_quota.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, EmptyBlob) {
// First delete all IDB's for the test origin
DeleteForOrigin(kFileOrigin);
EXPECT_EQ(0, RequestBlobFileCount(kFileOrigin)); // Start with no blob files.
const GURL test_url = GetTestUrl("indexeddb", "empty_blob.html");
// For some reason Android's futimes fails (EPERM) in this test. Do not assert
// file times on Android, but do so on other platforms. crbug.com/467247
// TODO(cmumford): Figure out why this is the case and fix if possible.
#if defined(OS_ANDROID)
SimpleTest(GURL(test_url.spec() + "#ignoreTimes"));
#else
SimpleTest(GURL(test_url.spec()));
#endif
// Test stores one blob and one file to disk, so expect two files.
EXPECT_EQ(2, RequestBlobFileCount(kFileOrigin));
}
// Very flaky on many bots. See crbug.com/459835
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed, DISABLED_BlobDidAck) {
SimpleTest(GetTestUrl("indexeddb", "blob_did_ack.html"));
// Wait for idle so that the blob ack has time to be received/processed by
// the browser process.
scoped_refptr<base::ThreadTestHelper> helper =
base::MakeRefCounted<base::ThreadTestHelper>(GetContext()->TaskRunner());
ASSERT_TRUE(helper->Run());
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(helper->Run());
content::ChromeBlobStorageContext* blob_context =
ChromeBlobStorageContext::GetFor(
shell()->web_contents()->GetBrowserContext());
EXPECT_EQ(0UL, blob_context->context()->blob_count());
}
// Flaky. See crbug.com/459835.
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithGCExposed,
DISABLED_BlobDidAckPrefetch) {
SimpleTest(GetTestUrl("indexeddb", "blob_did_ack_prefetch.html"));
// Wait for idle so that the blob ack has time to be received/processed by
// the browser process.
base::RunLoop().RunUntilIdle();
content::ChromeBlobStorageContext* blob_context =
ChromeBlobStorageContext::GetFor(
shell()->web_contents()->GetBrowserContext());
EXPECT_EQ(0UL, blob_context->context()->blob_count());
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, BlobsCountAgainstQuota) {
SimpleTest(GetTestUrl("indexeddb", "blobs_use_quota.html"));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteForOriginDeletesBlobs) {
SimpleTest(GetTestUrl("indexeddb", "write_4mb_blob.html"));
int64_t size = RequestUsage(kFileOrigin);
// This assertion assumes that we do not compress blobs.
EXPECT_GT(size, 4 << 20 /* 4 MB */);
DeleteForOrigin(kFileOrigin);
EXPECT_EQ(0, RequestUsage(kFileOrigin));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteForOriginIncognito) {
const GURL test_url = GetTestUrl("indexeddb", "fill_up_5k.html");
const Origin origin = Origin::Create(test_url);
Shell* browser = CreateOffTheRecordBrowser();
NavigateToURLBlockUntilNavigationsComplete(browser, test_url, 2);
EXPECT_GT(RequestUsage(origin, browser), 5 * 1024);
IndexedDBContextImpl* context = GetContext(browser);
base::RunLoop loop;
context->TaskRunner()->PostTask(FROM_HERE, base::BindLambdaForTesting([&]() {
context->DeleteForOrigin(origin);
loop.Quit();
}));
loop.Run();
EXPECT_EQ(0, RequestUsage(origin, browser));
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DiskFullOnCommit) {
// Ignore several preceding transactions:
// * The test calls deleteDatabase() which opens the backing store:
// #1: IndexedDBBackingStore::OpenBackingStore
// => IndexedDBBackingStore::SetUpMetadata
// #2: IndexedDBBackingStore::OpenBackingStore
// => IndexedDBBackingStore::CleanUpBlobJournal (no-op)
// * The test calls open(), to create a new database:
// #3: IndexedDBFactoryImpl::Open
// => IndexedDBDatabase::Create
// => IndexedDBBackingStore::CreateIDBDatabaseMetaData
// #4: IndexedDBTransaction::Commit - initial "versionchange" transaction
// * Once the connection is opened, the test runs:
// #5: IndexedDBTransaction::Commit - the test's "readwrite" transaction)
const int instance_num = 5;
const int call_num = 1;
FailOperation(FAIL_CLASS_LEVELDB_TRANSACTION, FAIL_METHOD_COMMIT_DISK_FULL,
instance_num, call_num);
SimpleTest(GetTestUrl("indexeddb", "disk_full_on_commit.html"));
}
namespace {
std::unique_ptr<net::test_server::HttpResponse> ServePath(
std::string request_path) {
base::FilePath resource_path =
content::GetTestFilePath("indexeddb", request_path.c_str());
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_OK);
std::string file_contents;
if (!base::ReadFileToString(resource_path, &file_contents))
NOTREACHED() << "could not read file " << resource_path;
http_response->set_content(file_contents);
return std::move(http_response);
}
void CompactIndexedDBBackingStore(scoped_refptr<IndexedDBContextImpl> context,
const Origin& origin) {
IndexedDBFactory* factory = context->GetIDBFactory();
std::pair<IndexedDBFactory::OriginDBMapIterator,
IndexedDBFactory::OriginDBMapIterator>
range = factory->GetOpenDatabasesForOrigin(origin);
if (range.first == range.second) // If no open db's for this origin
return;
// Compact the first db's backing store since all the db's are in the same
// backing store.
IndexedDBDatabase* db = range.first->second;
IndexedDBBackingStore* backing_store = db->backing_store();
backing_store->Compact();
}
void CorruptIndexedDBDatabase(IndexedDBContextImpl* context,
const Origin& origin,
base::WaitableEvent* signal_when_finished) {
CompactIndexedDBBackingStore(context, origin);
int num_files = 0;
int num_errors = 0;
const bool recursive = false;
for (const base::FilePath& idb_data_path : context->GetStoragePaths(origin)) {
base::FileEnumerator enumerator(
idb_data_path, recursive, base::FileEnumerator::FILES);
for (base::FilePath idb_file = enumerator.Next(); !idb_file.empty();
idb_file = enumerator.Next()) {
int64_t size(0);
GetFileSize(idb_file, &size);
if (idb_file.Extension() == FILE_PATH_LITERAL(".ldb")) {
num_files++;
base::File file(
idb_file, base::File::FLAG_WRITE | base::File::FLAG_OPEN_TRUNCATED);
if (file.IsValid()) {
// Was opened truncated, expand back to the original
// file size and fill with zeros (corrupting the file).
file.SetLength(size);
} else {
num_errors++;
}
}
}
VLOG(0) << "There were " << num_files << " in " << idb_data_path.value()
<< " with " << num_errors << " errors";
}
signal_when_finished->Signal();
}
const char s_corrupt_db_test_prefix[] = "/corrupt/test/";
std::unique_ptr<net::test_server::HttpResponse> CorruptDBRequestHandler(
IndexedDBContextImpl* context,
const Origin& origin,
const std::string& path,
IndexedDBBrowserTest* test,
const net::test_server::HttpRequest& request) {
std::string request_path;
if (path.find(s_corrupt_db_test_prefix) != std::string::npos)
request_path = request.relative_url.substr(
std::string(s_corrupt_db_test_prefix).size());
else
return std::unique_ptr<net::test_server::HttpResponse>();
// Remove the query string if present.
std::string request_query;
size_t query_pos = request_path.find('?');
if (query_pos != std::string::npos) {
request_query = request_path.substr(query_pos + 1);
request_path = request_path.substr(0, query_pos);
}
if (request_path == "corruptdb" && !request_query.empty()) {
VLOG(0) << "Requested to corrupt IndexedDB: " << request_query;
base::WaitableEvent signal_when_finished(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
context->TaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(&CorruptIndexedDBDatabase, base::ConstRef(context),
origin, &signal_when_finished));
signal_when_finished.Wait();
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_OK);
return std::move(http_response);
} else if (request_path == "fail" && !request_query.empty()) {
FailClass failure_class = FAIL_CLASS_NOTHING;
FailMethod failure_method = FAIL_METHOD_NOTHING;
int instance_num = 1;
int call_num = 1;
std::string fail_class;
std::string fail_method;
url::Component query(0, request_query.length()), key_pos, value_pos;
while (url::ExtractQueryKeyValue(
request_query.c_str(), &query, &key_pos, &value_pos)) {
std::string escaped_key(request_query.substr(key_pos.begin, key_pos.len));
std::string escaped_value(
request_query.substr(value_pos.begin, value_pos.len));
std::string key = net::UnescapeURLComponent(
escaped_key,
net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES |
net::UnescapeRule::PATH_SEPARATORS |
net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
std::string value = net::UnescapeURLComponent(
escaped_value,
net::UnescapeRule::NORMAL | net::UnescapeRule::SPACES |
net::UnescapeRule::PATH_SEPARATORS |
net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
if (key == "method")
fail_method = value;
else if (key == "class")
fail_class = value;
else if (key == "instNum")
instance_num = atoi(value.c_str());
else if (key == "callNum")
call_num = atoi(value.c_str());
else
NOTREACHED() << "Unknown param: \"" << key << "\"";
}
if (fail_class == "LevelDBTransaction") {
failure_class = FAIL_CLASS_LEVELDB_TRANSACTION;
if (fail_method == "Get")
failure_method = FAIL_METHOD_GET;
else if (fail_method == "Commit")
failure_method = FAIL_METHOD_COMMIT;
else
NOTREACHED() << "Unknown method: \"" << fail_method << "\"";
} else if (fail_class == "LevelDBIterator") {
failure_class = FAIL_CLASS_LEVELDB_ITERATOR;
if (fail_method == "Seek")
failure_method = FAIL_METHOD_SEEK;
else
NOTREACHED() << "Unknown method: \"" << fail_method << "\"";
} else {
NOTREACHED() << "Unknown class: \"" << fail_class << "\"";
}
DCHECK_GE(instance_num, 1);
DCHECK_GE(call_num, 1);
test->FailOperation(failure_class, failure_method, instance_num, call_num);
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse);
http_response->set_code(net::HTTP_OK);
return std::move(http_response);
}
return ServePath(request_path);
}
const char s_indexeddb_test_prefix[] = "/indexeddb/test/";
std::unique_ptr<net::test_server::HttpResponse> StaticFileRequestHandler(
const std::string& path,
IndexedDBBrowserTest* test,
const net::test_server::HttpRequest& request) {
if (path.find(s_indexeddb_test_prefix) == std::string::npos)
return std::unique_ptr<net::test_server::HttpResponse>();
std::string request_path =
request.relative_url.substr(std::string(s_indexeddb_test_prefix).size());
return ServePath(request_path);
}
} // namespace
IN_PROC_BROWSER_TEST_P(IndexedDBBrowserTest, OperationOnCorruptedOpenDatabase) {
ASSERT_TRUE(embedded_test_server()->Started() ||
embedded_test_server()->InitializeAndListen());
const Origin origin = Origin::Create(embedded_test_server()->base_url());
embedded_test_server()->RegisterRequestHandler(
base::Bind(&CorruptDBRequestHandler, base::Unretained(GetContext()),
origin, s_corrupt_db_test_prefix, this));
embedded_test_server()->StartAcceptingConnections();
std::string test_file = std::string(s_corrupt_db_test_prefix) +
"corrupted_open_db_detection.html#" + GetParam();
SimpleTest(embedded_test_server()->GetURL(test_file));
test_file =
std::string(s_corrupt_db_test_prefix) + "corrupted_open_db_recovery.html";
SimpleTest(embedded_test_server()->GetURL(test_file));
}
INSTANTIATE_TEST_SUITE_P(IndexedDBBrowserTestInstantiation,
IndexedDBBrowserTest,
::testing::Values("failGetBlobJournal",
"get",
"getAll",
"iterate",
"failTransactionCommit",
"clearObjectStore"));
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, DeleteCompactsBackingStore) {
const GURL test_url = GetTestUrl("indexeddb", "delete_compact.html");
SimpleTest(GURL(test_url.spec() + "#fill"));
int64_t after_filling = RequestUsage(kFileOrigin);
EXPECT_GT(after_filling, 0);
SimpleTest(GURL(test_url.spec() + "#purge"));
int64_t after_deleting = RequestUsage(kFileOrigin);
EXPECT_LT(after_deleting, after_filling);
// The above tests verify basic assertions - that filling writes data and
// deleting reduces the amount stored.
// The below tests make assumptions about implementation specifics, such as
// data compression, compaction efficiency, and the maximum amount of
// metadata and log data remains after a deletion. It is possible that
// changes to the implementation may require these constants to be tweaked.
const int kTestFillBytes = 1024 * 1024 * 5; // 5MB
EXPECT_GT(after_filling, kTestFillBytes);
const int kTestCompactBytes = 1024 * 10; // 10kB
EXPECT_LT(after_deleting, kTestCompactBytes);
}
// Complex multi-step (converted from pyauto) tests begin here.
// Verify null key path persists after restarting browser.
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_NullKeyPathPersistence) {
NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part1",
"pass - first run");
}
// Verify null key path persists after restarting browser.
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, NullKeyPathPersistence) {
NavigateAndWaitForTitle(shell(), "bug_90635.html", "#part2",
"pass - second run");
}
// Verify that a VERSION_CHANGE transaction is rolled back after a
// renderer/browser crash
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest,
PRE_PRE_VersionChangeCrashResilience) {
NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part1",
"pass - part1 - complete");
}
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, PRE_VersionChangeCrashResilience) {
NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part2",
"pass - part2 - crash me");
// Previously this test would abruptly terminate the browser process
// to ensure that the version update was not partially committed,
// which was possible in the very early implementation (circa 2011).
// This test no longer abruptly terminates the process, but the
// commit scheme has changed so it's not plausible any more anyway.
// TODO(jsbell): Delete or rename the test.
}
// Fails to cleanup GPU processes on swarming.
// http://crbug.com/552543
#if defined(OS_WIN)
#define MAYBE_VersionChangeCrashResilience DISABLED_VersionChangeCrashResilience
#else
#define MAYBE_VersionChangeCrashResilience VersionChangeCrashResilience
#endif
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest,
MAYBE_VersionChangeCrashResilience) {
NavigateAndWaitForTitle(shell(), "version_change_crash.html", "#part3",
"pass - part3 - rolled back");
}
// crbug.com/427529
// Disable this test for ASAN on Android because it takes too long to run.
#if defined(ANDROID) && defined(ADDRESS_SANITIZER)
#define MAYBE_ConnectionsClosedOnTabClose DISABLED_ConnectionsClosedOnTabClose
#else
#define MAYBE_ConnectionsClosedOnTabClose ConnectionsClosedOnTabClose
#endif
// Verify that open DB connections are closed when a tab is destroyed.
IN_PROC_BROWSER_TEST_F(
IndexedDBBrowserTest, MAYBE_ConnectionsClosedOnTabClose) {
NavigateAndWaitForTitle(shell(), "version_change_blocked.html", "#tab1",
"setVersion(2) complete");
// Start on a different URL to force a new renderer process.
Shell* new_shell = CreateBrowser();
NavigateToURL(new_shell, GURL(url::kAboutBlankURL));
NavigateAndWaitForTitle(new_shell, "version_change_blocked.html", "#tab2",
"setVersion(3) blocked");
base::string16 expected_title16(ASCIIToUTF16("setVersion(3) complete"));
TitleWatcher title_watcher(new_shell->web_contents(), expected_title16);
shell()->web_contents()->GetMainFrame()->GetProcess()->Shutdown(0);
shell()->Close();
EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
}
// Testing abort ordering for commit. Verifies that an explicitly committed
// transaction blocked by another pending transaction will be committed rather
// than aborted in the event that the page crashes before its committed.
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestWithExperimentalWebFeatures,
CommitContinuesOnAbort) {
// Sets up a database, opens four transactions against it such that two end
// up indefinitely blocked, one of which is explicitly committed.
NavigateAndWaitForTitle(shell(), "blocked_explicit_commit.html", "#tab1",
"transactions registered");
// Crashes the tab to cause the database set up above to force close with the
// blocked transactions still open.
shell()->web_contents()->GetMainFrame()->GetProcess()->Shutdown(0);
// Reopens the same page that was just crashed and inspects the database to
// see the results of the transactions that were open at time of crash.
Shell* new_shell = CreateBrowser();
GURL url = GetTestUrl("indexeddb", "blocked_explicit_commit.html");
url = GURL(url.spec() + "#tab2");
base::string16 expected_title16(
ASCIIToUTF16("transactions aborted and committed as expected"));
TitleWatcher title_watcher(new_shell->web_contents(), expected_title16);
title_watcher.AlsoWaitForTitle(
ASCIIToUTF16("fail - transactions did not abort and commit as expected"));
NavigateToURL(new_shell, url);
EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
}
// Verify that a "close" event is fired at database connections when
// the backing store is deleted.
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTest, ForceCloseEventTest) {
NavigateAndWaitForTitle(shell(), "force_close_event.html", nullptr,
"connection ready");
DeleteForOrigin(kFileOrigin);
base::string16 expected_title16(ASCIIToUTF16("connection closed"));
TitleWatcher title_watcher(shell()->web_contents(), expected_title16);
title_watcher.AlsoWaitForTitle(ASCIIToUTF16("connection closed with error"));
EXPECT_EQ(expected_title16, title_watcher.WaitAndGetTitle());
}
// The V2 schema corruption test runs in a separate class to avoid corrupting
// an IDB store that other tests use.
class IndexedDBBrowserTestV2SchemaCorruption : public IndexedDBBrowserTest {
public:
void SetUp() override {
GetTestClassFactory()->Reset();
IndexedDBClassFactory::SetIndexedDBClassFactoryGetter(GetIDBClassFactory);
ContentBrowserTest::SetUp();
}
};
// Verify the V2 schema corruption lifecycle:
// - create a current version backing store (v3 or later)
// - add an object store, some data, and an object that contains a blob
// - verify the object+blob are stored in the object store
// - verify the backing store doesn't have v2 schema corruption
// - force the schema to downgrade to v2
// - verify the backing store has v2 schema corruption
// - verify the object+blob can be fetched
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestV2SchemaCorruption, LifecycleTest) {
ASSERT_TRUE(embedded_test_server()->Started() ||
embedded_test_server()->InitializeAndListen());
const Origin origin = Origin::Create(embedded_test_server()->base_url());
embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
&StaticFileRequestHandler, s_indexeddb_test_prefix, this));
embedded_test_server()->StartAcceptingConnections();
// Set up the IndexedDB instance so it contains our reference data.
std::string test_file =
std::string(s_indexeddb_test_prefix) + "v2schemacorrupt_setup.html";
SimpleTest(embedded_test_server()->GetURL(test_file));
// Verify the backing store does not have corruption.
V2SchemaCorruptionStatus has_corruption =
RequestHasV2SchemaCorruption(origin);
ASSERT_EQ(has_corruption, V2SchemaCorruptionStatus::kNo);
// Revert schema to v2. This closes the targeted backing store.
bool schema_downgrade = RequestSchemaDowngrade(origin);
ASSERT_EQ(schema_downgrade, true);
// Re-open the backing store and verify it has corruption.
test_file =
std::string(s_indexeddb_test_prefix) + "v2schemacorrupt_reopen.html";
SimpleTest(embedded_test_server()->GetURL(test_file));
has_corruption = RequestHasV2SchemaCorruption(origin);
ASSERT_EQ(has_corruption, V2SchemaCorruptionStatus::kYes);
// Verify that the saved blob is get-able with a v2 backing store.
test_file =
std::string(s_indexeddb_test_prefix) + "v2schemacorrupt_verify.html";
SimpleTest(embedded_test_server()->GetURL(test_file));
}
class IndexedDBBrowserTestSingleProcess : public IndexedDBBrowserTest {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kSingleProcess);
}
};
// https://crbug.com/788788
#if defined(OS_ANDROID) && defined(ADDRESS_SANITIZER)
#define MAYBE_RenderThreadShutdownTest DISABLED_RenderThreadShutdownTest
#else
#define MAYBE_RenderThreadShutdownTest RenderThreadShutdownTest
#endif // defined(OS_ANDROID) && defined(ADDRESS_SANITIZER)
IN_PROC_BROWSER_TEST_F(IndexedDBBrowserTestSingleProcess,
MAYBE_RenderThreadShutdownTest) {
SimpleTest(GetTestUrl("indexeddb", "shutdown_with_requests.html"));
}
} // namespace content