ladybird/Libraries/LibDevTools/Actors/IndexedDBActor.cpp

325 lines
11 KiB
C++
Raw Permalink Normal View History

/*
* Copyright (c) 2026, Ladybird contributors
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <LibDevTools/Actors/IndexedDBActor.h>
#include <LibDevTools/Actors/TabActor.h>
#include <LibDevTools/DevToolsDelegate.h>
#include <LibDevTools/DevToolsServer.h>
#include <LibDevTools/StorageHelpers.h>
namespace DevTools {
NonnullRefPtr<IndexedDBActor> IndexedDBActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab)
{
return adopt_ref(*new IndexedDBActor(devtools, move(name), move(tab)));
}
IndexedDBActor::IndexedDBActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab)
: Actor(devtools, move(name))
, m_tab(move(tab))
{
if (auto tab = m_tab.strong_ref()) {
m_indexed_database_change_listener_id = devtools.delegate().add_indexed_database_change_listener(
tab->description(),
weak_callback(*this, [](auto& self, JsonObject update) {
self.on_indexed_database_changed(move(update));
}));
}
}
IndexedDBActor::~IndexedDBActor()
{
if (m_indexed_database_change_listener_id == 0)
return;
if (auto tab = m_tab.strong_ref())
devtools().delegate().remove_indexed_database_change_listener(tab->description(), m_indexed_database_change_listener_id);
}
JsonObject IndexedDBActor::serialize_storage(JsonObject hosts) const
{
if (hosts.is_empty()) {
if (auto tab = m_tab.strong_ref()) {
if (auto host = storage_host_for_url(tab->description().url); host.has_value())
hosts.set(*host, JsonArray {});
}
}
JsonObject traits;
traits.set("supportsAddItem"sv, false);
traits.set("supportsRemoveAll"sv, true);
traits.set("supportsRemoveAllSessionCookies"sv, false);
traits.set("supportsRemoveItem"sv, true);
JsonObject storage;
storage.set("actor"sv, name());
if (auto tab = m_tab.strong_ref()) {
storage.set("browsingContextID"sv, tab->description().id);
storage.set("innerWindowId"sv, tab->inner_window_id());
storage.set("resourceId"sv, MUST(String::formatted("indexed-db-{}", tab->inner_window_id())));
}
storage.set("hosts"sv, move(hosts));
storage.set("resourceKey"sv, "indexedDB"sv);
storage.set("traits"sv, move(traits));
return storage;
}
void IndexedDBActor::get_storage_resource(Function<void(JsonObject)> callback)
{
auto tab = m_tab.strong_ref();
if (!tab) {
callback(serialize_storage({}));
return;
}
devtools().delegate().inspect_indexed_database_storage(tab->description(),
[weak_self = make_weak_ptr<IndexedDBActor>(), callback = move(callback)](ErrorOr<JsonObject> hosts_or_error) mutable {
auto self = weak_self.strong_ref();
if (!self)
return;
callback(self->serialize_storage(hosts_or_error.is_error() ? JsonObject {} : hosts_or_error.release_value()));
});
}
void IndexedDBActor::handle_message(Message const& message)
{
if (message.type == "getFields"sv) {
get_fields(message);
return;
}
if (message.type == "getStoreObjects"sv) {
get_store_objects(message);
return;
}
if (message.type == "removeDatabase"sv) {
remove_database(message);
return;
}
if (message.type == "removeAll"sv) {
remove_all(message);
return;
}
if (message.type == "removeItem"sv) {
remove_item(message);
return;
}
send_unrecognized_packet_type_error(message);
}
void IndexedDBActor::get_fields(Message const& message)
{
auto sub_type = message.data.get_string("subType"sv);
JsonArray fields;
if (sub_type == "database"sv) {
fields.must_append(define_storage_field("objectStore"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("keyPath"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("autoIncrement"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("indexes"sv, StorageFieldType::Immutable));
} else if (sub_type == "object store"sv) {
fields.must_append(define_storage_field("name"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("value"sv, StorageFieldType::Immutable));
} else {
fields.must_append(define_storage_field("uniqueKey"sv, StorageFieldType::Private));
fields.must_append(define_storage_field("db"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("storage"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("origin"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("version"sv, StorageFieldType::Immutable));
fields.must_append(define_storage_field("objectStores"sv, StorageFieldType::Immutable));
}
JsonObject response;
response.set("value"sv, move(fields));
send_response(message, move(response));
}
void IndexedDBActor::send_inspection_error(Message const& message, Error const& error)
{
JsonObject response;
response.set("error"sv, "indexedDBInspectionFailed"sv);
response.set("message"sv, error.string_literal());
send_response(message, move(response));
}
void IndexedDBActor::get_store_objects(Message const& message)
{
auto host = get_required_parameter<String>(message, "host"sv);
if (!host.has_value())
return;
auto tab = m_tab.strong_ref();
if (!tab) {
send_inspection_error(message, Error::from_string_literal("Unable to locate tab"));
return;
}
Optional<JsonArray> names;
if (auto names_array = message.data.get_array("names"sv); names_array.has_value())
names = *names_array;
auto options = message.data.get_object("options"sv).value_or({});
devtools().delegate().inspect_indexed_database_objects(tab->description(), *host, move(names), move(options),
[weak_self = make_weak_ptr<IndexedDBActor>(), message_id = message.id](ErrorOr<JsonObject> result) mutable {
auto self = weak_self.strong_ref();
if (!self)
return;
if (result.is_error()) {
self->send_inspection_error({ .id = message_id }, result.error());
return;
}
self->send_response({ .id = message_id }, result.release_value());
});
}
void IndexedDBActor::remove_database(Message const& message)
{
auto host = get_required_parameter<String>(message, "host"sv);
auto name = get_required_parameter<String>(message, "name"sv);
if (!host.has_value() || !name.has_value())
return;
auto tab = m_tab.strong_ref();
if (!tab) {
send_inspection_error(message, Error::from_string_literal("Unable to locate tab"));
return;
}
devtools().delegate().delete_indexed_database(tab->description(), *host, *name,
[weak_self = make_weak_ptr<IndexedDBActor>(), message_id = message.id, host = *host, name = *name](ErrorOr<JsonObject> result) mutable {
auto self = weak_self.strong_ref();
if (!self)
return;
if (result.is_error()) {
self->send_inspection_error({ .id = message_id }, result.error());
return;
}
auto response = result.release_value();
auto blocked = response.get_bool("blocked"sv).value_or(false);
self->send_response({ .id = message_id }, move(response));
if (!blocked) {
JsonArray path;
path.must_append(name);
self->send_indexed_database_update("deleted"sv, host, path.serialized());
}
});
}
void IndexedDBActor::remove_all(Message const& message)
{
auto host = get_required_parameter<String>(message, "host"sv);
auto name = get_required_parameter<String>(message, "name"sv);
if (!host.has_value() || !name.has_value())
return;
auto tab = m_tab.strong_ref();
if (!tab) {
send_inspection_error(message, Error::from_string_literal("Unable to locate tab"));
return;
}
devtools().delegate().clear_indexed_database_object_store(tab->description(), *host, *name,
[weak_self = make_weak_ptr<IndexedDBActor>(), message_id = message.id, host = *host, name = *name](ErrorOr<JsonObject> result) mutable {
auto self = weak_self.strong_ref();
if (!self)
return;
if (result.is_error()) {
self->send_inspection_error({ .id = message_id }, result.error());
return;
}
auto response = result.release_value();
self->send_response({ .id = message_id }, move(response));
self->send_indexed_database_clear(host, name);
});
}
void IndexedDBActor::remove_item(Message const& message)
{
auto host = get_required_parameter<String>(message, "host"sv);
auto name = get_required_parameter<String>(message, "name"sv);
if (!host.has_value() || !name.has_value())
return;
auto tab = m_tab.strong_ref();
if (!tab) {
send_inspection_error(message, Error::from_string_literal("Unable to locate tab"));
return;
}
devtools().delegate().delete_indexed_database_record(tab->description(), *host, *name,
[weak_self = make_weak_ptr<IndexedDBActor>(), message_id = message.id, host = *host, name = *name](ErrorOr<JsonObject> result) mutable {
auto self = weak_self.strong_ref();
if (!self)
return;
if (result.is_error()) {
self->send_inspection_error({ .id = message_id }, result.error());
return;
}
auto response = result.release_value();
self->send_response({ .id = message_id }, move(response));
self->send_indexed_database_update("deleted"sv, host, name);
});
}
void IndexedDBActor::send_indexed_database_update(StringView update_type, String const& host, String const& name)
{
JsonArray paths;
paths.must_append(name);
JsonObject hosts;
hosts.set(host, move(paths));
JsonObject indexed_database;
indexed_database.set("indexedDB"sv, move(hosts));
JsonObject update;
update.set(update_type, move(indexed_database));
on_indexed_database_changed(move(update));
}
void IndexedDBActor::send_indexed_database_clear(String const& host, String const& name)
{
JsonArray paths;
paths.must_append(name);
JsonObject hosts;
hosts.set(host, move(paths));
JsonObject data;
data.set("clearedHostsOrPaths"sv, move(hosts));
JsonObject message;
message.set("type"sv, "storesCleared"sv);
message.set("data"sv, move(data));
send_message(move(message));
}
void IndexedDBActor::on_indexed_database_changed(JsonObject update)
{
JsonObject message;
message.set("type"sv, "storesUpdate"sv);
message.set("data"sv, move(update));
send_message(move(message));
}
}