blob: d85f00733603fd47d6341f4298ddf0a093149129 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/indexed_db/instance/backing_store_util.h"
#include "base/containers/to_vector.h"
#include "base/values.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/browser/indexed_db/instance/backing_store.h"
#include "crypto/hash.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_key_path.h"
#include "third_party/blink/public/common/indexeddb/indexeddb_metadata.h"
#include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom.h"
namespace content::indexed_db {
namespace {
base::DictValue DatabaseMetadataToDictValue(const BackingStore::Database& db) {
base::DictValue result;
const blink::IndexedDBDatabaseMetadata& metadata = db.GetMetadata();
result.Set("name", metadata.name);
result.Set("version", static_cast<int>(metadata.version));
base::ListValue object_stores;
for (const auto& [_, object_store] : metadata.object_stores) {
base::DictValue object_store_dict;
object_store_dict.Set("name", object_store.name);
object_store_dict.Set("id", static_cast<int>(object_store.id));
object_store_dict.Set("auto_increment", object_store.auto_increment);
auto key_path_to_dict = [](const blink::IndexedDBKeyPath& key_path) {
base::DictValue key_path_dict;
key_path_dict.Set("type", static_cast<int>(key_path.type()));
if (key_path.type() == blink::mojom::IDBKeyPathType::String) {
key_path_dict.Set("string", key_path.string());
} else if (key_path.type() == blink::mojom::IDBKeyPathType::Array) {
base::ListValue key_path_list =
base::ListValue::with_capacity(key_path.array().size());
for (const std::u16string& component : key_path.array()) {
key_path_list.Append(component);
}
key_path_dict.Set("path", std::move(key_path_list));
} else {
CHECK_EQ(key_path.type(), blink::mojom::IDBKeyPathType::Null);
}
return key_path_dict;
};
object_store_dict.Set("key_path", key_path_to_dict(object_store.key_path));
base::ListValue indexes;
for (const auto& [_, index] : object_store.indexes) {
base::DictValue index_dict;
index_dict.Set("name", index.name);
index_dict.Set("id", static_cast<int>(index.id));
index_dict.Set("key_path", key_path_to_dict(index.key_path));
index_dict.Set("unique", index.unique);
index_dict.Set("multi_entry", index.multi_entry);
indexes.Append(std::move(index_dict));
}
object_store_dict.Set("indexes", std::move(indexes));
object_stores.Append(std::move(object_store_dict));
}
result.Set("object_stores", std::move(object_stores));
return result;
}
base::ListValue CursorToListValue(std::unique_ptr<BackingStore::Cursor>& cursor,
bool include_primary_key,
size_t key_count) {
base::ListValue records = base::ListValue::with_capacity(key_count);
while (cursor) {
base::DictValue record;
std::string key_string = cursor->GetKey().DebugString();
if (key_string.size() > 256) {
record.Set(
"key_hash",
base::ToVector(crypto::hash::Sha256(base::as_byte_span(key_string))));
} else {
record.Set("key", std::move(key_string));
}
if (include_primary_key) {
key_string = cursor->GetPrimaryKey().DebugString();
if (key_string.size() > 256) {
record.Set("primary_key_hash", base::ToVector(crypto::hash::Sha256(
base::as_byte_span(key_string))));
} else {
record.Set("primary_key", std::move(key_string));
}
}
record.Set("value_hash",
base::ToVector(crypto::hash::Sha256(cursor->GetValue().bits)));
// Include some limited metadata for blobs (and other external objects).
base::ListValue external_objects;
for (const IndexedDBExternalObject& object :
cursor->GetValue().external_objects) {
base::DictValue object_dict;
object_dict.Set("type", static_cast<int>(object.object_type()));
object_dict.Set("size", static_cast<int>(object.size()));
external_objects.Append(std::move(object_dict));
}
record.Set("external_objects", std::move(external_objects));
records.Append(std::move(record));
StatusOr<bool> continue_result = cursor->Continue();
if (!continue_result.has_value()) {
records.Append(continue_result.error().ToString());
return records;
}
if (!*continue_result) {
break;
}
}
return records;
}
base::DictValue IndexToDictValue(
const blink::IndexedDBObjectStoreMetadata& object_store,
const blink::IndexedDBIndexMetadata& index,
BackingStore::Transaction& txn) {
base::DictValue contents;
// This is technically unnecessary.
StatusOr<uint32_t> key_count =
txn.GetIndexKeyCount(object_store.id, index.id, /*key_range=*/{});
if (!key_count.has_value()) {
contents.Set("record_count_error", key_count.error().ToString());
return contents;
}
contents.Set("record_count", static_cast<int>(*key_count));
// Print all records via Cursor.
StatusOr<std::unique_ptr<BackingStore::Cursor>> cursor =
txn.OpenIndexCursor(object_store.id, index.id, /*key_range=*/{},
blink::mojom::IDBCursorDirection::Next);
if (!cursor.has_value()) {
contents.Set("cursor_error", cursor.error().ToString());
return contents;
}
contents.Set(
"records",
CursorToListValue(*cursor, /*include_primary_key=*/true, *key_count));
return contents;
}
base::DictValue ObjectStoreToDictValue(
const blink::IndexedDBObjectStoreMetadata& object_store,
BackingStore::Transaction& txn) {
base::DictValue contents;
// This is technically unnecessary.
StatusOr<uint32_t> key_count =
txn.GetObjectStoreKeyCount(object_store.id, /*key_range=*/{});
if (!key_count.has_value()) {
contents.Set("record_count_error", key_count.error().ToString());
return contents;
}
contents.Set("record_count", static_cast<int>(*key_count));
// Print all records via Cursor.
StatusOr<std::unique_ptr<BackingStore::Cursor>> cursor =
txn.OpenObjectStoreCursor(object_store.id, /*key_range=*/{},
blink::mojom::IDBCursorDirection::Next);
if (!cursor.has_value()) {
contents.Set("cursor_error", cursor.error().ToString());
return contents;
}
contents.Set(
"records",
CursorToListValue(*cursor, /*include_primary_key=*/false, *key_count));
base::ListValue indexes =
base::ListValue::with_capacity(object_store.indexes.size());
for (const auto& [_, index] : object_store.indexes) {
indexes.Append(IndexToDictValue(object_store, index, txn));
}
contents.Set("indexes", std::move(indexes));
return contents;
}
base::DictValue DatabaseContentsToDictValue(BackingStore::Database& db) {
base::DictValue contents;
auto txn =
db.CreateTransaction(blink::mojom::IDBTransactionDurability::Default,
blink::mojom::IDBTransactionMode::ReadOnly);
if (!txn->Begin({}).ok()) {
contents.Set("error", "Failed to begin read-only transaction");
return contents;
}
base::ListValue object_stores =
base::ListValue::with_capacity(db.GetMetadata().object_stores.size());
for (const auto& [_, object_store] : db.GetMetadata().object_stores) {
object_stores.Append(ObjectStoreToDictValue(object_store, *txn));
}
contents.Set("object_stores", std::move(object_stores));
return contents;
}
} // namespace
base::DictValue DumpDatabase(BackingStore::Database& db) {
base::DictValue result;
result.Set("metadata", DatabaseMetadataToDictValue(db));
result.Set("contents", DatabaseContentsToDictValue(db));
return result;
}
} // namespace content::indexed_db