Skip to content

Commit 52b53e5

Browse files
Lubrsiawesomekling
authored andcommitted
LibWeb/IndexedDB: Remove spin_until from waiting for connection closure
1 parent e6dc52a commit 52b53e5

File tree

11 files changed

+294
-77
lines changed

11 files changed

+294
-77
lines changed

Libraries/LibWeb/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ set(SOURCES
682682
IndexedDB/IDBVersionChangeEvent.cpp
683683
IndexedDB/Internal/Algorithms.cpp
684684
IndexedDB/Internal/Database.cpp
685+
IndexedDB/Internal/IDBDatabaseObserver.cpp
685686
IndexedDB/Internal/IDBRequestObserver.cpp
686687
IndexedDB/Internal/Index.cpp
687688
IndexedDB/Internal/Key.cpp

Libraries/LibWeb/Forward.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,7 @@ class Database;
803803
class IDBCursor;
804804
class IDBCursorWithValue;
805805
class IDBDatabase;
806+
class IDBDatabaseObserver;
806807
class IDBFactory;
807808
class IDBIndex;
808809
class IDBKeyRange;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
namespace Web::IndexedDB {
10+
11+
enum ConnectionState {
12+
Open,
13+
Closed,
14+
};
15+
16+
}

Libraries/LibWeb/IndexedDB/IDBDatabase.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <LibWeb/IndexedDB/IDBDatabase.h>
1212
#include <LibWeb/IndexedDB/IDBObjectStore.h>
1313
#include <LibWeb/IndexedDB/Internal/Algorithms.h>
14+
#include <LibWeb/IndexedDB/Internal/IDBDatabaseObserver.h>
1415

1516
namespace Web::IndexedDB {
1617

@@ -42,6 +43,7 @@ void IDBDatabase::initialize(JS::Realm& realm)
4243
void IDBDatabase::visit_edges(Visitor& visitor)
4344
{
4445
Base::visit_edges(visitor);
46+
visitor.visit(m_database_observers_being_notified);
4547
visitor.visit(m_object_store_set);
4648
visitor.visit(m_associated_database);
4749
visitor.visit(m_transactions);
@@ -241,4 +243,24 @@ WebIDL::ExceptionOr<GC::Ref<IDBTransaction>> IDBDatabase::transaction(Variant<St
241243
return transaction;
242244
}
243245

246+
void IDBDatabase::register_database_observer(Badge<IDBDatabaseObserver>, IDBDatabaseObserver& database_observer)
247+
{
248+
auto result = m_database_observers.set(database_observer);
249+
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
250+
}
251+
252+
void IDBDatabase::unregister_database_observer(Badge<IDBDatabaseObserver>, IDBDatabaseObserver& database_observer)
253+
{
254+
bool was_removed = m_database_observers.remove(database_observer);
255+
VERIFY(was_removed);
256+
}
257+
258+
void IDBDatabase::set_state(ConnectionState state)
259+
{
260+
m_state = state;
261+
notify_each_database_observer([](IDBDatabaseObserver const& request_observer) {
262+
return request_observer.connection_state_changed_observer();
263+
});
264+
}
265+
244266
}

Libraries/LibWeb/IndexedDB/IDBDatabase.h

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <LibWeb/Bindings/IDBDatabasePrototype.h>
1111
#include <LibWeb/DOM/EventTarget.h>
1212
#include <LibWeb/HTML/DOMStringList.h>
13+
#include <LibWeb/IndexedDB/ConnectionState.h>
1314
#include <LibWeb/IndexedDB/IDBRequest.h>
1415
#include <LibWeb/IndexedDB/IDBTransaction.h>
1516
#include <LibWeb/IndexedDB/Internal/Database.h>
@@ -37,19 +38,14 @@ class IDBDatabase : public DOM::EventTarget {
3738
WEB_PLATFORM_OBJECT(IDBDatabase, DOM::EventTarget);
3839
GC_DECLARE_ALLOCATOR(IDBDatabase);
3940

40-
enum ConnectionState {
41-
Open,
42-
Closed,
43-
};
44-
4541
public:
4642
virtual ~IDBDatabase() override;
4743

4844
[[nodiscard]] static GC::Ref<IDBDatabase> create(JS::Realm&, Database&);
4945

5046
void set_version(u64 version) { m_version = version; }
5147
void set_close_pending(bool close_pending) { m_close_pending = close_pending; }
52-
void set_state(ConnectionState state) { m_state = state; }
48+
void set_state(ConnectionState state);
5349

5450
[[nodiscard]] String uuid() const { return m_uuid; }
5551
[[nodiscard]] String name() const { return m_name; }
@@ -84,13 +80,36 @@ class IDBDatabase : public DOM::EventTarget {
8480
void set_onversionchange(WebIDL::CallbackType*);
8581
WebIDL::CallbackType* onversionchange();
8682

83+
void register_database_observer(Badge<IDBDatabaseObserver>, IDBDatabaseObserver&);
84+
void unregister_database_observer(Badge<IDBDatabaseObserver>, IDBDatabaseObserver&);
85+
8786
protected:
8887
explicit IDBDatabase(JS::Realm&, Database&);
8988

9089
virtual void initialize(JS::Realm&) override;
9190
virtual void visit_edges(Visitor& visitor) override;
9291

9392
private:
93+
template<typename GetNotifier, typename... Args>
94+
void notify_each_database_observer(GetNotifier&& get_notifier, Args&&... args)
95+
{
96+
ScopeGuard guard { [&]() { m_database_observers_being_notified.clear_with_capacity(); } };
97+
m_database_observers_being_notified.ensure_capacity(m_database_observers.size());
98+
99+
for (auto observer : m_database_observers)
100+
m_database_observers_being_notified.unchecked_append(observer);
101+
102+
for (auto database_observer : m_database_observers_being_notified) {
103+
if (auto notifier = get_notifier(*database_observer))
104+
notifier->function()(forward<Args>(args)...);
105+
}
106+
}
107+
108+
// IDBDatabase should not visit IDBDatabaseObserver to avoid leaks.
109+
// It's responsibility of object that requires IDBDatabaseObserver to keep it alive.
110+
HashTable<GC::RawRef<IDBDatabaseObserver>> m_database_observers;
111+
Vector<GC::Ref<IDBDatabaseObserver>> m_database_observers_being_notified;
112+
94113
u64 m_version { 0 };
95114
String m_name;
96115

Libraries/LibWeb/IndexedDB/IDBRequest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ void IDBRequest::unregister_request_observer(Badge<IDBRequestObserver>, IDBReque
128128
void IDBRequest::set_processed(bool processed)
129129
{
130130
m_processed = processed;
131-
notify_each_request_observer([&](IDBRequestObserver const& request_observer) {
131+
notify_each_request_observer([](IDBRequestObserver const& request_observer) {
132132
return request_observer.request_processed_changed_observer();
133133
});
134134
}

Libraries/LibWeb/IndexedDB/Internal/Algorithms.cpp

Lines changed: 44 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -164,51 +164,43 @@ void open_a_database_connection(JS::Realm& realm, StorageAPI::StorageKey storage
164164
// 4. If any of the connections in openConnections are still not closed,
165165
// queue a database task to fire a version change event named blocked at request with db’s version and version.
166166
for (auto const& entry : open_connections) {
167-
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
167+
if (entry->state() != ConnectionState::Closed) {
168168
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db, version]() {
169169
fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), version);
170170
}));
171171
}
172172
}
173173

174174
// 5. Wait until all connections in openConnections are closed.
175-
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() {
176-
if constexpr (IDB_DEBUG) {
177-
dbgln("open_a_database_connection: waiting for step 10.5");
178-
dbgln("open connections: {}", open_connections.size());
179-
for (auto const& connection : open_connections) {
180-
dbgln(" - {}", connection->uuid());
181-
}
182-
}
183-
184-
for (auto const& entry : open_connections) {
185-
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
186-
return false;
187-
}
175+
if constexpr (IDB_DEBUG) {
176+
dbgln("open_a_database_connection: waiting for step 10.5");
177+
dbgln("open connections: {}", open_connections.size());
178+
for (auto const& open_connection : open_connections) {
179+
dbgln(" - {}", open_connection->uuid());
188180
}
181+
}
189182

190-
return true;
191-
}));
192-
193-
// 6. Run upgrade a database using connection, version and request.
194-
upgrade_a_database(realm, connection, version, request);
183+
db->wait_for_connections_to_close(open_connections, GC::create_function(realm.heap(), [&realm, connection, version, request, on_complete] {
184+
// 6. Run upgrade a database using connection, version and request.
185+
upgrade_a_database(realm, connection, version, request);
195186

196-
// 7. If connection was closed, return a newly created "AbortError" DOMException and abort these steps.
197-
if (connection->state() == IDBDatabase::ConnectionState::Closed) {
198-
on_complete->function()(WebIDL::AbortError::create(realm, "Connection was closed"_utf16));
199-
return;
200-
}
187+
// 7. If connection was closed, return a newly created "AbortError" DOMException and abort these steps.
188+
if (connection->state() == ConnectionState::Closed) {
189+
on_complete->function()(WebIDL::AbortError::create(realm, "Connection was closed"_utf16));
190+
return;
191+
}
201192

202-
// 8. If request's error is set, run the steps to close a database connection with connection,
203-
// return a newly created "AbortError" DOMException and abort these steps.
204-
if (request->has_error()) {
205-
close_a_database_connection(*connection);
206-
on_complete->function()(WebIDL::AbortError::create(realm, "Upgrade transaction was aborted"_utf16));
207-
return;
208-
}
193+
// 8. If request's error is set, run the steps to close a database connection with connection,
194+
// return a newly created "AbortError" DOMException and abort these steps.
195+
if (request->has_error()) {
196+
close_a_database_connection(*connection);
197+
on_complete->function()(WebIDL::AbortError::create(realm, "Upgrade transaction was aborted"_utf16));
198+
return;
199+
}
209200

210-
// 11. Return connection.
211-
on_complete->function()(connection);
201+
// 11. Return connection.
202+
on_complete->function()(connection);
203+
}));
212204
});
213205

214206
if (task_counter_state) {
@@ -396,7 +388,7 @@ void close_a_database_connection(GC::Ref<IDBDatabase> connection, bool forced)
396388
return true;
397389
}));
398390

399-
connection->set_state(IDBDatabase::ConnectionState::Closed);
391+
connection->set_state(ConnectionState::Closed);
400392

401393
// 4. If the forced flag is true, then fire an event named close at connection.
402394
if (forced)
@@ -535,44 +527,36 @@ void delete_a_database(JS::Realm& realm, StorageAPI::StorageKey storage_key, Str
535527
auto after_all = GC::create_function(realm.heap(), [&realm, open_connections, db, storage_key = move(storage_key), name = move(name), on_complete] {
536528
// 8. If any of the connections in openConnections are still not closed, queue a database task to fire a version change event named blocked at request with db’s version and null.
537529
for (auto const& entry : open_connections) {
538-
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
530+
if (entry->state() != ConnectionState::Closed) {
539531
queue_a_database_task(GC::create_function(realm.vm().heap(), [&realm, entry, db]() {
540532
fire_a_version_change_event(realm, HTML::EventNames::blocked, *entry, db->version(), {});
541533
}));
542534
}
543535
}
544536

545537
// 9. Wait until all connections in openConnections are closed.
546-
HTML::main_thread_event_loop().spin_until(GC::create_function(realm.vm().heap(), [open_connections]() {
547-
if constexpr (IDB_DEBUG) {
548-
dbgln("delete_a_database: waiting for step 9");
549-
dbgln("open connections: {}", open_connections.size());
550-
for (auto const& connection : open_connections) {
551-
dbgln(" - {}", connection->uuid());
552-
}
538+
if constexpr (IDB_DEBUG) {
539+
dbgln("delete_a_database: waiting for step 9");
540+
dbgln("open connections: {}", open_connections.size());
541+
for (auto const& connection : open_connections) {
542+
dbgln(" - {}", connection->uuid());
553543
}
544+
}
554545

555-
for (auto const& entry : open_connections) {
556-
if (entry->state() != IDBDatabase::ConnectionState::Closed) {
557-
return false;
558-
}
546+
db->wait_for_connections_to_close(open_connections, GC::create_function(realm.heap(), [&realm, db, storage_key = move(storage_key), name = move(name), on_complete] {
547+
// 10. Let version be db’s version.
548+
auto version = db->version();
549+
550+
// 11. Delete db. If this fails for any reason, return an appropriate error (e.g. "QuotaExceededError" or "UnknownError" DOMException).
551+
auto maybe_deleted = Database::delete_for_key_and_name(storage_key, name);
552+
if (maybe_deleted.is_error()) {
553+
on_complete->function()(WebIDL::OperationError::create(realm, "Unable to delete database"_utf16));
554+
return;
559555
}
560556

561-
return true;
557+
// 12. Return version.
558+
on_complete->function()(version);
562559
}));
563-
564-
// 10. Let version be db’s version.
565-
auto version = db->version();
566-
567-
// 11. Delete db. If this fails for any reason, return an appropriate error (e.g. "QuotaExceededError" or "UnknownError" DOMException).
568-
auto maybe_deleted = Database::delete_for_key_and_name(storage_key, name);
569-
if (maybe_deleted.is_error()) {
570-
on_complete->function()(WebIDL::OperationError::create(realm, "Unable to delete database"_utf16));
571-
return;
572-
}
573-
574-
// 12. Return version.
575-
on_complete->function()(version);
576560
});
577561

578562
if (task_counter_state) {

0 commit comments

Comments
 (0)