LibDevTools+LibWeb: Send IndexedDB change updates
The Storage panel expects storesUpdate messages after watched storage changes. Summarize committed IndexedDB mutation logs into DevTools paths and forward them through WebContent so Firefox can refresh database, object-store, and record rows without polling. Firefox's behaviour is less than ideal here. A lot of things don't update automatically even inspecting a page in Firefox. Some things (like new databases) won't show up until you fully refresh the page. So that makes it a bit hard to know that we're doing things correctly. As far as I can tell, we are at least behaving as well as Firefox requires. We do have one workaround: Firefox doesn't display record updates without a manual refresh, and in fact any change messages for them show up as rows in the host's database table. So for now, we filter them out to avoid visual weirdness in the inspector.
This commit is contained in:
parent
cb47dbfc7a
commit
e667aaaab1
22 changed files with 430 additions and 1 deletions
|
|
@ -23,9 +23,23 @@ IndexedDBActor::IndexedDBActor(DevToolsServer& devtools, String name, WeakPtr<Ta
|
|||
: 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() = default;
|
||||
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
|
||||
{
|
||||
|
|
@ -155,4 +169,12 @@ void IndexedDBActor::get_store_objects(Message const& 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ private:
|
|||
|
||||
void get_fields(Message const&);
|
||||
void get_store_objects(Message const&);
|
||||
void on_indexed_database_changed(JsonObject);
|
||||
|
||||
JsonObject serialize_storage(JsonObject hosts) const;
|
||||
void send_inspection_error(Message const&, Error const&);
|
||||
|
||||
WeakPtr<TabActor> m_tab;
|
||||
u64 m_indexed_database_change_listener_id { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@ public:
|
|||
using OnIndexedDBInspectionComplete = Function<void(ErrorOr<JsonObject>)>;
|
||||
virtual void inspect_indexed_database_storage(TabDescription const&, OnIndexedDBInspectionComplete) const { }
|
||||
virtual void inspect_indexed_database_objects(TabDescription const&, String const&, Optional<JsonArray>, JsonObject, OnIndexedDBInspectionComplete) const { }
|
||||
using OnIndexedDatabaseChange = Function<void(JsonObject)>;
|
||||
virtual u64 add_indexed_database_change_listener(TabDescription const&, OnIndexedDatabaseChange) const { return 0; }
|
||||
virtual void remove_indexed_database_change_listener(TabDescription const&, u64) const { }
|
||||
|
||||
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
|
||||
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
|
||||
|
|
|
|||
|
|
@ -187,6 +187,48 @@ JsonObject serialize_objects(Web::DOM::Document& document, String const& host, J
|
|||
host);
|
||||
}
|
||||
|
||||
static void append_indexed_database_update(JsonObject& update, StringView type, JsonArray paths, String const& host)
|
||||
{
|
||||
if (paths.is_empty())
|
||||
return;
|
||||
|
||||
JsonObject hosts;
|
||||
hosts.set(host, move(paths));
|
||||
|
||||
JsonObject indexed_database;
|
||||
indexed_database.set("indexedDB"sv, move(hosts));
|
||||
|
||||
update.set(type, move(indexed_database));
|
||||
}
|
||||
|
||||
static JsonArray serialize_update_paths(Vector<Web::IndexedDB::TransactionChange> const& changes)
|
||||
{
|
||||
JsonArray paths;
|
||||
paths.ensure_capacity(changes.size());
|
||||
for (auto const& change : changes) {
|
||||
// FIXME: Firefox treats added IndexedDB update paths as storage tree additions, so record-level changes must
|
||||
// not be sent there or they appear as blank rows in the selected host view.
|
||||
// Remove this filter once Firefox supports record updates.
|
||||
if (change.key.has_value())
|
||||
continue;
|
||||
paths.must_append(indexed_database_path(change.database_name, change.object_store_name, change.key));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
JsonObject serialize_update(String const& url, Web::IndexedDB::TransactionChanges const& changes)
|
||||
{
|
||||
JsonObject update;
|
||||
auto host = storage_host_for_url(url);
|
||||
if (!host.has_value())
|
||||
return update;
|
||||
|
||||
append_indexed_database_update(update, "added"sv, serialize_update_paths(changes.added), *host);
|
||||
append_indexed_database_update(update, "changed"sv, serialize_update_paths(changes.changed), *host);
|
||||
append_indexed_database_update(update, "deleted"sv, serialize_update_paths(changes.deleted), *host);
|
||||
return update;
|
||||
}
|
||||
|
||||
static ErrorOr<Web::IndexedDB::InspectionPath> parse_required_indexed_database_path(String const& name)
|
||||
{
|
||||
auto path = parse_indexed_database_path(JsonValue { name });
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@
|
|||
#include <AK/String.h>
|
||||
#include <LibDevTools/Forward.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/IndexedDB/TransactionChanges.h>
|
||||
|
||||
namespace DevTools::IndexedDB {
|
||||
|
||||
DEVTOOLS_API JsonObject serialize_storage(Web::DOM::Document&);
|
||||
DEVTOOLS_API JsonObject serialize_objects(Web::DOM::Document&, String const& host, JsonValue const& names, JsonValue const& options);
|
||||
DEVTOOLS_API JsonObject serialize_update(String const& url, Web::IndexedDB::TransactionChanges const&);
|
||||
DEVTOOLS_API ErrorOr<JsonObject> delete_database(Web::DOM::Document&, String const& host, String const& name);
|
||||
DEVTOOLS_API ErrorOr<JsonObject> clear_object_store(Web::DOM::Document&, String const& host, String const& name);
|
||||
DEVTOOLS_API ErrorOr<JsonObject> delete_record(Web::DOM::Document&, String const& host, String const& name);
|
||||
|
|
|
|||
|
|
@ -7,12 +7,15 @@
|
|||
#include <LibWeb/Bindings/IDBDatabase.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Crypto/Crypto.h>
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/HTML/EventNames.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/IndexedDB/IDBDatabase.h>
|
||||
#include <LibWeb/IndexedDB/IDBIndex.h>
|
||||
#include <LibWeb/IndexedDB/IDBObjectStore.h>
|
||||
#include <LibWeb/IndexedDB/IDBTransaction.h>
|
||||
#include <LibWeb/IndexedDB/Internal/Algorithms.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
|
|
@ -238,4 +241,23 @@ void IDBTransaction::discard_mutation_logs()
|
|||
m_store_mutation_logs.clear();
|
||||
}
|
||||
|
||||
void IDBTransaction::notify_devtools_of_committed_changes()
|
||||
{
|
||||
auto document = HTML::relevant_settings_object(*this).responsible_document();
|
||||
if (!document)
|
||||
return;
|
||||
|
||||
if (!document->page().client().has_active_devtools_client())
|
||||
return;
|
||||
|
||||
TransactionChanges changes;
|
||||
auto database_name = m_connection->associated_database()->name();
|
||||
for (auto const& entry : m_store_mutation_logs)
|
||||
entry.log->append_changes(database_name, entry.store->name(), changes);
|
||||
if (changes.is_empty())
|
||||
return;
|
||||
|
||||
document->page().client().page_did_update_indexed_database(document->url().serialize(), changes);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ public:
|
|||
void set_onerror(WebIDL::CallbackType*);
|
||||
WebIDL::CallbackType* onerror();
|
||||
|
||||
void notify_devtools_of_committed_changes();
|
||||
|
||||
protected:
|
||||
explicit IDBTransaction(JS::Realm&, GC::Ref<IDBDatabase>, Bindings::IDBTransactionMode, Bindings::IDBTransactionDurability, Vector<GC::Ref<ObjectStore>>);
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
|
|
|
|||
|
|
@ -885,6 +885,7 @@ void commit_a_transaction(JS::Realm& realm, GC::Ref<IDBTransaction> transaction)
|
|||
transaction->connection()->associated_database()->set_upgrade_transaction(nullptr);
|
||||
|
||||
// AD-HOC: Discard mutation logs now that changes are permanent.
|
||||
transaction->notify_devtools_of_committed_changes();
|
||||
transaction->discard_mutation_logs();
|
||||
|
||||
// 2. Set transaction’s state to finished.
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
*/
|
||||
|
||||
#include <LibWeb/IndexedDB/IDBDatabase.h>
|
||||
#include <LibWeb/IndexedDB/Inspection.h>
|
||||
#include <LibWeb/IndexedDB/Internal/Database.h>
|
||||
#include <LibWeb/IndexedDB/Internal/Index.h>
|
||||
#include <LibWeb/IndexedDB/Internal/Key.h>
|
||||
#include <LibWeb/IndexedDB/Internal/MutationLog.h>
|
||||
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
|
||||
|
||||
|
|
@ -120,6 +122,71 @@ void MutationLog::note_index_record_stored(GC::Ref<Index> index, IndexRecord rec
|
|||
m_entries.append(IndexRecordStored { index, record });
|
||||
}
|
||||
|
||||
void MutationLog::append_changes(String const& database_name, String const& object_store_name, TransactionChanges& changes) const
|
||||
{
|
||||
Vector<GC::Ref<Key>> stored_keys;
|
||||
Vector<GC::Ref<Key>> changed_keys;
|
||||
Vector<GC::Ref<Key>> deleted_keys;
|
||||
|
||||
auto remove_key = [](Vector<GC::Ref<Key>>& keys, Key& key) {
|
||||
return keys.remove_first_matching([&](auto const& existing) {
|
||||
return Key::equals(existing, GC::Ref { key });
|
||||
});
|
||||
};
|
||||
|
||||
auto append_deleted_key = [&](Key& key) {
|
||||
if (remove_key(stored_keys, key))
|
||||
return;
|
||||
remove_key(changed_keys, key);
|
||||
deleted_keys.append(GC::Ref { key });
|
||||
};
|
||||
|
||||
auto append_stored_key = [&](Key& key) {
|
||||
if (remove_key(deleted_keys, key)) {
|
||||
changed_keys.append(GC::Ref { key });
|
||||
return;
|
||||
}
|
||||
stored_keys.append(GC::Ref { key });
|
||||
};
|
||||
|
||||
for (auto const& entry : m_entries) {
|
||||
entry.visit(
|
||||
[&](ObjectStoreCreated const&) {
|
||||
changes.added.append({ database_name, object_store_name });
|
||||
},
|
||||
[&](ObjectStoreDeleted const&) {
|
||||
changes.deleted.append({ database_name, object_store_name });
|
||||
},
|
||||
[&](ObjectStoreRenamed const& e) {
|
||||
changes.deleted.append({ database_name, e.old_name });
|
||||
changes.added.append({ database_name, object_store_name });
|
||||
},
|
||||
[&](IndexCreated const&) {},
|
||||
[&](IndexDeleted const&) {},
|
||||
[&](IndexRenamed const&) {},
|
||||
[&](KeyGeneratorChanged const&) {
|
||||
},
|
||||
[&](RecordsDeleted const& e) {
|
||||
for (auto const& record : e.records)
|
||||
append_deleted_key(*record.key);
|
||||
},
|
||||
[&](RecordStored const& e) {
|
||||
append_stored_key(*e.key);
|
||||
},
|
||||
[&](IndexRecordsDeleted const&) {},
|
||||
[&](IndexRecordStored const&) {});
|
||||
}
|
||||
|
||||
for (auto const& key : stored_keys)
|
||||
changes.added.append({ database_name, object_store_name, serialize_key_for_inspection(key) });
|
||||
|
||||
for (auto const& key : changed_keys)
|
||||
changes.changed.append({ database_name, object_store_name, serialize_key_for_inspection(key) });
|
||||
|
||||
for (auto const& key : deleted_keys)
|
||||
changes.deleted.append({ database_name, object_store_name, serialize_key_for_inspection(key) });
|
||||
}
|
||||
|
||||
void MutationLog::revert(ObjectStore& store, GC::Ref<Database> database, GC::Ref<IDBDatabase> connection)
|
||||
{
|
||||
revert_entries(store, 0, database, connection);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibWeb/IndexedDB/IDBRecord.h>
|
||||
#include <LibWeb/IndexedDB/Internal/KeyGenerator.h>
|
||||
#include <LibWeb/IndexedDB/TransactionChanges.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
|
|
@ -67,6 +68,8 @@ public:
|
|||
// Clear the log without reverting (used after successful transaction commit).
|
||||
void clear() { m_entries.clear(); }
|
||||
|
||||
void append_changes(String const& database_name, String const& object_store_name, TransactionChanges&) const;
|
||||
|
||||
[[nodiscard]] size_t position() const { return m_entries.size(); }
|
||||
|
||||
protected:
|
||||
|
|
|
|||
33
Libraries/LibWeb/IndexedDB/TransactionChanges.h
Normal file
33
Libraries/LibWeb/IndexedDB/TransactionChanges.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2026, Ladybird contributors
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/JsonValue.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/String.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace Web::IndexedDB {
|
||||
|
||||
struct TransactionChange {
|
||||
String database_name;
|
||||
String object_store_name;
|
||||
Optional<JsonValue> key {};
|
||||
};
|
||||
|
||||
struct TransactionChanges {
|
||||
Vector<TransactionChange> added;
|
||||
Vector<TransactionChange> changed;
|
||||
Vector<TransactionChange> deleted;
|
||||
|
||||
bool is_empty() const
|
||||
{
|
||||
return added.is_empty() && changed.is_empty() && deleted.is_empty();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
#include <LibWeb/HTML/TokenizedFeatures.h>
|
||||
#include <LibWeb/HTML/WebViewHints.h>
|
||||
#include <LibWeb/HTML/WorkerAgentForward.h>
|
||||
#include <LibWeb/IndexedDB/TransactionChanges.h>
|
||||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
|
|
@ -504,6 +505,7 @@ public:
|
|||
virtual Vector<String> page_did_request_storage_keys([[maybe_unused]] Web::StorageAPI::StorageEndpointType storage_endpoint, [[maybe_unused]] String const& storage_key) { return {}; }
|
||||
virtual void page_did_clear_storage([[maybe_unused]] Web::StorageAPI::StorageEndpointType storage_endpoint, [[maybe_unused]] String const& storage_key) { }
|
||||
virtual void page_did_broadcast_storage_change([[maybe_unused]] Web::StorageAPI::StorageEndpointType storage_endpoint, [[maybe_unused]] String const& url, [[maybe_unused]] Optional<String> const& key, [[maybe_unused]] Optional<String> const& old_value, [[maybe_unused]] Optional<String> const& new_value) { }
|
||||
virtual void page_did_update_indexed_database([[maybe_unused]] String const& url, [[maybe_unused]] IndexedDB::TransactionChanges const&) { }
|
||||
virtual void page_did_update_resource_count(i32) { }
|
||||
struct NewWebViewResult {
|
||||
GC::Ptr<Page> page;
|
||||
|
|
|
|||
|
|
@ -1998,6 +1998,52 @@ void Application::inspect_indexed_database_objects(DevTools::TabDescription cons
|
|||
view->inspect_indexed_database_objects(host, move(names), move(options), move(on_complete));
|
||||
}
|
||||
|
||||
void Application::delete_indexed_database(DevTools::TabDescription const& description, String const& host, String const& name, OnIndexedDBInspectionComplete on_complete) const
|
||||
{
|
||||
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||
if (!view.has_value()) {
|
||||
on_complete(Error::from_string_literal("Unable to locate tab"));
|
||||
return;
|
||||
}
|
||||
|
||||
view->delete_indexed_database(host, name, move(on_complete));
|
||||
}
|
||||
|
||||
void Application::clear_indexed_database_object_store(DevTools::TabDescription const& description, String const& host, String const& name, OnIndexedDBInspectionComplete on_complete) const
|
||||
{
|
||||
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||
if (!view.has_value()) {
|
||||
on_complete(Error::from_string_literal("Unable to locate tab"));
|
||||
return;
|
||||
}
|
||||
|
||||
view->clear_indexed_database_object_store(host, name, move(on_complete));
|
||||
}
|
||||
|
||||
void Application::delete_indexed_database_record(DevTools::TabDescription const& description, String const& host, String const& name, OnIndexedDBInspectionComplete on_complete) const
|
||||
{
|
||||
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||
if (!view.has_value()) {
|
||||
on_complete(Error::from_string_literal("Unable to locate tab"));
|
||||
return;
|
||||
}
|
||||
|
||||
view->delete_indexed_database_record(host, name, move(on_complete));
|
||||
}
|
||||
|
||||
u64 Application::add_indexed_database_change_listener(DevTools::TabDescription const& description, OnIndexedDatabaseChange on_indexed_database_change) const
|
||||
{
|
||||
if (auto view = ViewImplementation::find_view_by_id(description.id); view.has_value())
|
||||
return view->add_indexed_database_change_listener(move(on_indexed_database_change));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Application::remove_indexed_database_change_listener(DevTools::TabDescription const& description, u64 listener_id) const
|
||||
{
|
||||
if (auto view = ViewImplementation::find_view_by_id(description.id); view.has_value())
|
||||
view->remove_indexed_database_change_listener(listener_id);
|
||||
}
|
||||
|
||||
void Application::inspect_tab(DevTools::TabDescription const& description, OnTabInspectionComplete on_complete) const
|
||||
{
|
||||
auto view = ViewImplementation::find_view_by_id(description.id);
|
||||
|
|
|
|||
|
|
@ -285,6 +285,11 @@ private:
|
|||
virtual void remove_storage_change_listener(DevTools::TabDescription const&, u64) const override;
|
||||
virtual void inspect_indexed_database_storage(DevTools::TabDescription const&, OnIndexedDBInspectionComplete) const override;
|
||||
virtual void inspect_indexed_database_objects(DevTools::TabDescription const&, String const&, Optional<JsonArray>, JsonObject, OnIndexedDBInspectionComplete) const override;
|
||||
virtual void delete_indexed_database(DevTools::TabDescription const&, String const&, String const&, OnIndexedDBInspectionComplete) const override;
|
||||
virtual void clear_indexed_database_object_store(DevTools::TabDescription const&, String const&, String const&, OnIndexedDBInspectionComplete) const override;
|
||||
virtual void delete_indexed_database_record(DevTools::TabDescription const&, String const&, String const&, OnIndexedDBInspectionComplete) const override;
|
||||
virtual u64 add_indexed_database_change_listener(DevTools::TabDescription const&, OnIndexedDatabaseChange) const override;
|
||||
virtual void remove_indexed_database_change_listener(DevTools::TabDescription const&, u64) const override;
|
||||
virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete) const override;
|
||||
virtual void inspect_accessibility_tree(DevTools::TabDescription const&, OnAccessibilityTreeInspectionComplete) const override;
|
||||
virtual void listen_for_dom_properties(DevTools::TabDescription const&, OnDOMNodePropertiesReceived) const override;
|
||||
|
|
|
|||
|
|
@ -718,6 +718,24 @@ void ViewImplementation::remove_storage_change_listener(u64 listener_id)
|
|||
m_storage_change_listeners.remove(listener_id);
|
||||
}
|
||||
|
||||
void ViewImplementation::notify_indexed_database_changed(JsonObject update)
|
||||
{
|
||||
for (auto& listener : m_indexed_database_change_listeners)
|
||||
listener.value(update);
|
||||
}
|
||||
|
||||
u64 ViewImplementation::add_indexed_database_change_listener(DevTools::DevToolsDelegate::OnIndexedDatabaseChange on_indexed_database_change)
|
||||
{
|
||||
auto listener_id = m_next_indexed_database_change_listener_id++;
|
||||
m_indexed_database_change_listeners.set(listener_id, move(on_indexed_database_change));
|
||||
return listener_id;
|
||||
}
|
||||
|
||||
void ViewImplementation::remove_indexed_database_change_listener(u64 listener_id)
|
||||
{
|
||||
m_indexed_database_change_listeners.remove(listener_id);
|
||||
}
|
||||
|
||||
ErrorOr<Core::SharedVersionIndex> ViewImplementation::ensure_document_cookie_version_index(Badge<WebContentClient>, String const& domain)
|
||||
{
|
||||
return m_document_cookie_version_indices.try_ensure(domain, [&]() -> ErrorOr<Core::SharedVersionIndex> {
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ public:
|
|||
void notify_cookies_changed(HashTable<String> const& changed_domains, ReadonlySpan<HTTP::Cookie::Cookie> page_cookies, ReadonlySpan<HTTP::Cookie::Cookie> host_cookies);
|
||||
void listen_for_host_cookie_changes(DevTools::DevToolsDelegate::OnHostCookieChange);
|
||||
void stop_listening_for_host_cookie_changes();
|
||||
void notify_indexed_database_changed(JsonObject);
|
||||
u64 add_indexed_database_change_listener(DevTools::DevToolsDelegate::OnIndexedDatabaseChange);
|
||||
void remove_indexed_database_change_listener(u64 listener_id);
|
||||
ErrorOr<Core::SharedVersionIndex> ensure_document_cookie_version_index(Badge<WebContentClient>, String const&);
|
||||
Optional<Core::SharedVersion> document_cookie_version(URL::URL const&) const;
|
||||
|
||||
|
|
@ -604,6 +607,8 @@ protected:
|
|||
Core::AnonymousBuffer m_document_cookie_version_buffer;
|
||||
HashMap<String, Core::SharedVersionIndex> m_document_cookie_version_indices;
|
||||
DevTools::DevToolsDelegate::OnHostCookieChange m_on_host_cookie_change;
|
||||
HashMap<u64, DevTools::DevToolsDelegate::OnIndexedDatabaseChange> m_indexed_database_change_listeners;
|
||||
u64 m_next_indexed_database_change_listener_id { 1 };
|
||||
|
||||
HashMap<u64, DevTools::DevToolsDelegate::OnStorageChange> m_storage_change_listeners;
|
||||
u64 m_next_storage_change_listener_id { 1 };
|
||||
|
|
|
|||
|
|
@ -1086,6 +1086,12 @@ void WebContentClient::did_change_storage_item(u64 page_id, Web::StorageAPI::Sto
|
|||
}
|
||||
}
|
||||
|
||||
void WebContentClient::did_update_indexed_database(u64 page_id, String update)
|
||||
{
|
||||
if (auto view = view_for_page_id(page_id); view.has_value())
|
||||
view->notify_indexed_database_changed(parse_json(update, "IndexedDB update"sv));
|
||||
}
|
||||
|
||||
void WebContentClient::did_post_broadcast_channel_message(u64, Web::HTML::BroadcastChannelMessage message)
|
||||
{
|
||||
WebContentClient::for_each_client([&](auto& client) {
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ private:
|
|||
virtual Messages::WebContentClient::DidRequestStorageKeysResponse did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) override;
|
||||
virtual void did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) override;
|
||||
virtual void did_change_storage_item(u64 page_id, Web::StorageAPI::StorageEndpointType storage_endpoint, String url, Optional<String> key, Optional<String> old_value, Optional<String> new_value) override;
|
||||
virtual void did_update_indexed_database(u64 page_id, String update) override;
|
||||
virtual void did_post_broadcast_channel_message(u64 page_id, Web::HTML::BroadcastChannelMessage message) override;
|
||||
virtual Messages::WebContentClient::DidRequestNewWebViewResponse did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab, Web::HTML::WebViewHints) override;
|
||||
virtual void did_request_activate_tab(u64 page_id) override;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <AK/Math.h>
|
||||
#include <LibCore/Process.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibDevTools/IndexedDBSerialization.h>
|
||||
#include <LibGfx/Bitmap.h>
|
||||
#include <LibGfx/ShareableBitmap.h>
|
||||
#include <LibHTTP/Cookie/ParsedCookie.h>
|
||||
|
|
@ -778,6 +779,18 @@ void PageClient::page_did_broadcast_storage_change(Web::StorageAPI::StorageEndpo
|
|||
client().async_did_change_storage_item(m_id, storage_endpoint, url, key, old_value, new_value);
|
||||
}
|
||||
|
||||
void PageClient::page_did_update_indexed_database(String const& url, Web::IndexedDB::TransactionChanges const& changes)
|
||||
{
|
||||
if (!has_devtools_client())
|
||||
return;
|
||||
|
||||
auto update = DevTools::IndexedDB::serialize_update(url, changes);
|
||||
if (update.is_empty())
|
||||
return;
|
||||
|
||||
client().async_did_update_indexed_database(m_id, update.serialized());
|
||||
}
|
||||
|
||||
void PageClient::page_did_post_broadcast_channel_message(Web::HTML::BroadcastChannelMessage const& message)
|
||||
{
|
||||
client().async_did_post_broadcast_channel_message(m_id, message);
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ private:
|
|||
virtual Vector<String> page_did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String const& storage_key) override;
|
||||
virtual void page_did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String const& storage_key) override;
|
||||
virtual void page_did_broadcast_storage_change(Web::StorageAPI::StorageEndpointType storage_endpoint, String const& url, Optional<String> const& key, Optional<String> const& old_value, Optional<String> const& new_value) override;
|
||||
virtual void page_did_update_indexed_database(String const& url, Web::IndexedDB::TransactionChanges const&) override;
|
||||
virtual void page_did_update_resource_count(i32) override;
|
||||
virtual NewWebViewResult page_did_request_new_web_view(Web::HTML::ActivateTab, Web::HTML::WebViewHints, Web::HTML::TokenizedFeature::NoOpener) override;
|
||||
virtual void page_did_request_activate_tab() override;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ endpoint WebContentClient
|
|||
did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) => (Vector<String> keys)
|
||||
did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) => ()
|
||||
did_change_storage_item(u64 page_id, Web::StorageAPI::StorageEndpointType storage_endpoint, String url, Optional<String> key, Optional<String> old_value, Optional<String> new_value) =|
|
||||
did_update_indexed_database(u64 page_id, String update) =|
|
||||
did_post_broadcast_channel_message(u64 page_id, Web::HTML::BroadcastChannelMessage message) =|
|
||||
|
||||
did_update_resource_count(u64 page_id, i32 count_waiting) =|
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <LibDevTools/Connection.h>
|
||||
#include <LibDevTools/DevToolsDelegate.h>
|
||||
#include <LibDevTools/DevToolsServer.h>
|
||||
#include <LibDevTools/IndexedDBSerialization.h>
|
||||
#include <LibHTTP/Cookie/ParsedCookie.h>
|
||||
#include <LibHTTP/Header.h>
|
||||
#include <LibRequests/RequestTimingInfo.h>
|
||||
|
|
@ -831,6 +832,44 @@ public:
|
|||
callback(make_indexed_database_store_objects(names));
|
||||
}
|
||||
|
||||
virtual void delete_indexed_database(DevTools::TabDescription const&, String const& host, String const& name, OnIndexedDBInspectionComplete callback) const override
|
||||
{
|
||||
++delete_indexed_database_call_count;
|
||||
last_indexed_database_host = host;
|
||||
last_indexed_database_name = name;
|
||||
callback(JsonObject {});
|
||||
}
|
||||
|
||||
virtual void clear_indexed_database_object_store(DevTools::TabDescription const&, String const& host, String const& name, OnIndexedDBInspectionComplete callback) const override
|
||||
{
|
||||
++clear_indexed_database_object_store_call_count;
|
||||
last_indexed_database_host = host;
|
||||
last_indexed_database_name = name;
|
||||
callback(JsonObject {});
|
||||
}
|
||||
|
||||
virtual void delete_indexed_database_record(DevTools::TabDescription const&, String const& host, String const& name, OnIndexedDBInspectionComplete callback) const override
|
||||
{
|
||||
++delete_indexed_database_record_call_count;
|
||||
last_indexed_database_host = host;
|
||||
last_indexed_database_name = name;
|
||||
callback(JsonObject {});
|
||||
}
|
||||
|
||||
virtual u64 add_indexed_database_change_listener(DevTools::TabDescription const&, OnIndexedDatabaseChange callback) const override
|
||||
{
|
||||
auto listener_id = next_indexed_database_change_listener_id++;
|
||||
indexed_database_change_listeners.set(listener_id, move(callback));
|
||||
++add_indexed_database_change_listener_call_count;
|
||||
return listener_id;
|
||||
}
|
||||
|
||||
virtual void remove_indexed_database_change_listener(DevTools::TabDescription const&, u64 listener_id) const override
|
||||
{
|
||||
indexed_database_change_listeners.remove(listener_id);
|
||||
++remove_indexed_database_change_listener_call_count;
|
||||
}
|
||||
|
||||
virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete callback) const override
|
||||
{
|
||||
++inspect_tab_call_count;
|
||||
|
|
@ -1245,6 +1284,13 @@ public:
|
|||
return fixture_session_storage_items;
|
||||
}
|
||||
|
||||
void emit_indexed_database_change(JsonObject update) const
|
||||
{
|
||||
VERIFY(!indexed_database_change_listeners.is_empty());
|
||||
for (auto& listener : indexed_database_change_listeners)
|
||||
listener.value(update);
|
||||
}
|
||||
|
||||
mutable Function<void(WebView::DOMNodeProperties)> on_dom_node_properties;
|
||||
mutable Function<void(WebView::Mutation)> on_dom_mutation;
|
||||
mutable Function<void(Web::CSS::StyleSheetIdentifier const&, String)> on_style_sheet_source;
|
||||
|
|
@ -1257,6 +1303,7 @@ public:
|
|||
mutable Function<void(Vector<HTTP::Cookie::Cookie>)> on_host_cookie_change;
|
||||
mutable HashMap<u64, Function<void(DevToolsDelegate::StorageChange)>> storage_change_listeners;
|
||||
String tab_url { "https://example.test/"_string };
|
||||
mutable HashMap<u64, Function<void(JsonObject)>> indexed_database_change_listeners;
|
||||
|
||||
struct NavigationListener {
|
||||
Function<void(String)> on_navigation_started;
|
||||
|
|
@ -1287,6 +1334,12 @@ public:
|
|||
mutable bool fail_clear_storage { false };
|
||||
mutable size_t inspect_indexed_database_storage_call_count { 0 };
|
||||
mutable size_t inspect_indexed_database_objects_call_count { 0 };
|
||||
mutable size_t delete_indexed_database_call_count { 0 };
|
||||
mutable size_t clear_indexed_database_object_store_call_count { 0 };
|
||||
mutable size_t delete_indexed_database_record_call_count { 0 };
|
||||
mutable size_t add_indexed_database_change_listener_call_count { 0 };
|
||||
mutable size_t remove_indexed_database_change_listener_call_count { 0 };
|
||||
mutable u64 next_indexed_database_change_listener_id { 1 };
|
||||
mutable size_t inspect_accessibility_tree_call_count { 0 };
|
||||
mutable size_t listen_for_dom_properties_call_count { 0 };
|
||||
mutable size_t stop_listening_for_dom_properties_call_count { 0 };
|
||||
|
|
@ -2549,6 +2602,85 @@ TEST_CASE(storage_indexed_database_store_objects)
|
|||
EXPECT_EQ(record.get_string("value"sv).value(), "{\"name\":\"Ada\"}"sv);
|
||||
}
|
||||
|
||||
TEST_CASE(storage_indexed_database_change_events)
|
||||
{
|
||||
auto session = create_session();
|
||||
auto& client = *session->client;
|
||||
(void)client.read_message();
|
||||
|
||||
auto indexed_database_actor = get_indexed_database_actor(client);
|
||||
EXPECT_EQ(session->delegate.add_indexed_database_change_listener_call_count, 1u);
|
||||
|
||||
JsonArray added;
|
||||
added.must_append(indexed_database_path("fixtures (default)"sv, "people"sv));
|
||||
|
||||
JsonArray record_path;
|
||||
record_path.must_append("fixtures (default)"sv);
|
||||
record_path.must_append("people"sv);
|
||||
record_path.must_append(1);
|
||||
JsonArray changed;
|
||||
changed.must_append(record_path.serialized());
|
||||
|
||||
JsonArray deleted;
|
||||
deleted.must_append(indexed_database_path("empty (default)"sv));
|
||||
|
||||
JsonObject added_hosts;
|
||||
added_hosts.set("https://example.test"sv, move(added));
|
||||
JsonObject added_types;
|
||||
added_types.set("indexedDB"sv, move(added_hosts));
|
||||
|
||||
JsonObject changed_hosts;
|
||||
changed_hosts.set("https://example.test"sv, move(changed));
|
||||
JsonObject changed_types;
|
||||
changed_types.set("indexedDB"sv, move(changed_hosts));
|
||||
|
||||
JsonObject deleted_hosts;
|
||||
deleted_hosts.set("https://example.test"sv, move(deleted));
|
||||
JsonObject deleted_types;
|
||||
deleted_types.set("indexedDB"sv, move(deleted_hosts));
|
||||
|
||||
JsonObject update;
|
||||
update.set("added"sv, move(added_types));
|
||||
update.set("changed"sv, move(changed_types));
|
||||
update.set("deleted"sv, move(deleted_types));
|
||||
session->delegate.emit_indexed_database_change(move(update));
|
||||
|
||||
auto stores_update = read_packet_with_type(client, "storesUpdate"sv);
|
||||
EXPECT_EQ(stores_update.get_string("from"sv).value(), indexed_database_actor);
|
||||
|
||||
auto added_paths = get_indexed_database_update_paths(stores_update, "added"sv);
|
||||
EXPECT_EQ(added_paths.size(), 1u);
|
||||
EXPECT_EQ(added_paths.at(0).as_string(), indexed_database_path("fixtures (default)"sv, "people"sv));
|
||||
|
||||
auto changed_paths = get_indexed_database_update_paths(stores_update, "changed"sv);
|
||||
EXPECT_EQ(changed_paths.size(), 1u);
|
||||
EXPECT_EQ(changed_paths.at(0).as_string(), record_path.serialized());
|
||||
|
||||
auto deleted_paths = get_indexed_database_update_paths(stores_update, "deleted"sv);
|
||||
EXPECT_EQ(deleted_paths.size(), 1u);
|
||||
EXPECT_EQ(deleted_paths.at(0).as_string(), indexed_database_path("empty (default)"sv));
|
||||
}
|
||||
|
||||
TEST_CASE(storage_indexed_database_serializes_live_tree_updates)
|
||||
{
|
||||
Web::IndexedDB::TransactionChanges changes;
|
||||
changes.added.append({ "fixtures"_string, "people"_string });
|
||||
changes.added.append({ "fixtures"_string, "people"_string, JsonValue { 1 } });
|
||||
changes.changed.append({ "fixtures"_string, "people"_string, JsonValue { 2 } });
|
||||
changes.deleted.append({ "fixtures"_string, "people"_string, JsonValue { 3 } });
|
||||
|
||||
auto update = DevTools::IndexedDB::serialize_update("https://example.test/page"_string, changes);
|
||||
|
||||
auto added_paths = update.get_object("added"sv)
|
||||
->get_object("indexedDB"sv)
|
||||
->get_array("https://example.test"sv)
|
||||
.release_value();
|
||||
EXPECT_EQ(added_paths.size(), 1u);
|
||||
EXPECT_EQ(added_paths.at(0).as_string(), indexed_database_path("fixtures (default)"sv, "people"sv));
|
||||
EXPECT(!update.get_object("changed"sv).has_value());
|
||||
EXPECT(!update.get_object("deleted"sv).has_value());
|
||||
}
|
||||
|
||||
TEST_CASE(storage_cookie_store_objects)
|
||||
{
|
||||
auto session = create_session();
|
||||
|
|
|
|||
Loading…
Reference in a new issue