Reviewed by Nate Chapin.
Refactor IDBRequest and IDBTransaction a bit
https://bugs.webkit.org/show_bug.cgi?id=53565
There were a lot of subtle issues with the way IDBTransaction
and IDBRequest used to be written. This cleans a lot of them up
and largely simplifies the logic. Using EventQueue rather than
timers is one example of the simplification.
* bindings/scripts/CodeGeneratorV8.pm:
* dom/EventQueue.cpp:
(WebCore::EventQueue::enqueueEvent):
(WebCore::EventQueue::dispatchEvent):
* storage/IDBCursor.cpp:
(WebCore::IDBCursor::continueFunction):
* storage/IDBRequest.cpp:
(WebCore::IDBRequest::create):
(WebCore::IDBRequest::IDBRequest):
(WebCore::IDBRequest::resetReadyState):
(WebCore::IDBRequest::onError):
(WebCore::IDBRequest::onSuccess):
(WebCore::IDBRequest::dispatchEvent):
(WebCore::IDBRequest::enqueueEvent):
(WebCore::IDBRequest::eventTargetData):
(WebCore::IDBRequest::ensureEventTargetData):
* storage/IDBRequest.h:
(WebCore::IDBRequest::dispatchEvent):
* storage/IDBTransaction.cpp:
(WebCore::IDBTransaction::create):
(WebCore::IDBTransaction::IDBTransaction):
(WebCore::IDBTransaction::objectStore):
(WebCore::IDBTransaction::abort):
(WebCore::IDBTransaction::onAbort):
(WebCore::IDBTransaction::onComplete):
(WebCore::IDBTransaction::onTimeout):
(WebCore::IDBTransaction::canSuspend):
(WebCore::IDBTransaction::stop):
(WebCore::IDBTransaction::enqueueEvent):
(WebCore::IDBTransaction::eventTargetData):
(WebCore::IDBTransaction::ensureEventTargetData):
* storage/IDBTransaction.h:
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@77650
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2011-02-03 Jeremy Orlow <jorlow@chromium.org>
+
+ Reviewed by Nate Chapin.
+
+ Refactor IDBRequest and IDBTransaction a bit
+ https://bugs.webkit.org/show_bug.cgi?id=53565
+
+ There were a lot of subtle issues with the way IDBTransaction
+ and IDBRequest used to be written. This cleans a lot of them up
+ and largely simplifies the logic. Using EventQueue rather than
+ timers is one example of the simplification.
+
+ * bindings/scripts/CodeGeneratorV8.pm:
+ * dom/EventQueue.cpp:
+ (WebCore::EventQueue::enqueueEvent):
+ (WebCore::EventQueue::dispatchEvent):
+ * storage/IDBCursor.cpp:
+ (WebCore::IDBCursor::continueFunction):
+ * storage/IDBRequest.cpp:
+ (WebCore::IDBRequest::create):
+ (WebCore::IDBRequest::IDBRequest):
+ (WebCore::IDBRequest::resetReadyState):
+ (WebCore::IDBRequest::onError):
+ (WebCore::IDBRequest::onSuccess):
+ (WebCore::IDBRequest::dispatchEvent):
+ (WebCore::IDBRequest::enqueueEvent):
+ (WebCore::IDBRequest::eventTargetData):
+ (WebCore::IDBRequest::ensureEventTargetData):
+ * storage/IDBRequest.h:
+ (WebCore::IDBRequest::dispatchEvent):
+ * storage/IDBTransaction.cpp:
+ (WebCore::IDBTransaction::create):
+ (WebCore::IDBTransaction::IDBTransaction):
+ (WebCore::IDBTransaction::objectStore):
+ (WebCore::IDBTransaction::abort):
+ (WebCore::IDBTransaction::onAbort):
+ (WebCore::IDBTransaction::onComplete):
+ (WebCore::IDBTransaction::onTimeout):
+ (WebCore::IDBTransaction::canSuspend):
+ (WebCore::IDBTransaction::stop):
+ (WebCore::IDBTransaction::enqueueEvent):
+ (WebCore::IDBTransaction::eventTargetData):
+ (WebCore::IDBTransaction::ensureEventTargetData):
+ * storage/IDBTransaction.h:
+
2011-02-01 Jeremy Orlow <jorlow@chromium.org>
Reviewed by Nate Chapin.
return 1 if $type eq "Worker";
return 1 if $type eq "SharedWorker";
return 1 if $type eq "IDBRequest";
+ return 1 if $type eq "IDBTransaction";
return 1 if $type eq "FileReader";
return 1 if $type eq "FileWriter";
return 0;
void EventQueue::enqueueEvent(PassRefPtr<Event> event)
{
- ASSERT(event->target()->toNode() || event->target()->toDOMWindow());
+ ASSERT(event->target());
m_queuedEvents.append(event);
if (!m_pendingEventTimer->isActive())
else if (eventTarget->toDOMWindow())
eventTarget->toDOMWindow()->dispatchEvent(event, 0);
else
- ASSERT_NOT_REACHED();
+ eventTarget->dispatchEvent(event);
}
}
if (m_request->resetReadyState(m_transaction.get()))
m_backend->continueFunction(key, m_request, ec);
else
- ASSERT_NOT_REACHED();
+ ec = IDBDatabaseException::NOT_ALLOWED_ERR;
}
PassRefPtr<IDBRequest> IDBCursor::deleteFunction(ScriptExecutionContext* context, ExceptionCode& ec)
#if ENABLE(INDEXED_DATABASE)
-#include "Event.h"
+#include "Document.h"
#include "EventException.h"
#include "EventListener.h"
#include "EventNames.h"
+#include "EventQueue.h"
#include "IDBCursor.h"
#include "IDBDatabase.h"
#include "IDBIndex.h"
#include "IDBObjectStore.h"
#include "IDBPendingTransactionMonitor.h"
#include "IDBSuccessEvent.h"
-#include "ScriptExecutionContext.h"
namespace WebCore {
+PassRefPtr<IDBRequest> IDBRequest::create(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface* transaction)
+{
+ return adoptRef(new IDBRequest(context, source, transaction));
+}
+
IDBRequest::IDBRequest(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface* transaction)
: ActiveDOMObject(context, this)
, m_source(source)
, m_transaction(transaction)
- , m_timer(this, &IDBRequest::timerFired)
, m_readyState(LOADING)
{
if (m_transaction)
bool IDBRequest::resetReadyState(IDBTransactionBackendInterface* transaction)
{
- ASSERT(m_readyState == DONE);
- m_readyState = LOADING;
- ASSERT(!m_transaction);
+ ASSERT(scriptExecutionContext());
+ if (m_readyState != DONE)
+ return false;
+
m_transaction = transaction;
+ m_readyState = LOADING;
+
IDBPendingTransactionMonitor::removePendingTransaction(m_transaction.get());
+
return true;
}
void IDBRequest::onError(PassRefPtr<IDBDatabaseError> error)
{
- scheduleEvent(0, error);
+ enqueueEvent(IDBErrorEvent::create(m_source, *error));
}
void IDBRequest::onSuccess(PassRefPtr<IDBCursorBackendInterface> backend)
{
- scheduleEvent(IDBAny::create(IDBCursor::create(backend, this, m_transaction.get())), 0);
+ enqueueEvent(IDBSuccessEvent::create(m_source, IDBAny::create(IDBCursor::create(backend, this, m_transaction.get()))));
}
void IDBRequest::onSuccess(PassRefPtr<IDBDatabaseBackendInterface> backend)
{
- scheduleEvent(IDBAny::create(IDBDatabase::create(backend)), 0);
+ enqueueEvent(IDBSuccessEvent::create(m_source, IDBAny::create(IDBDatabase::create(backend))));
}
void IDBRequest::onSuccess(PassRefPtr<IDBIndexBackendInterface> backend)
{
- scheduleEvent(IDBAny::create(IDBIndex::create(backend, m_transaction.get())), 0);
+ ASSERT_NOT_REACHED(); // FIXME: This method should go away.
}
void IDBRequest::onSuccess(PassRefPtr<IDBKey> idbKey)
{
- scheduleEvent(IDBAny::create(idbKey), 0);
+ enqueueEvent(IDBSuccessEvent::create(m_source, IDBAny::create(idbKey)));
}
void IDBRequest::onSuccess(PassRefPtr<IDBObjectStoreBackendInterface> backend)
{
- // FIXME: This function should go away once createObjectStore is sync.
- scheduleEvent(IDBAny::create(IDBObjectStore::create(backend, m_transaction.get())), 0);
+ ASSERT_NOT_REACHED(); // FIXME: This method should go away.
}
void IDBRequest::onSuccess(PassRefPtr<IDBTransactionBackendInterface> prpBackend)
{
+ if (!scriptExecutionContext())
+ return;
+
RefPtr<IDBTransactionBackendInterface> backend = prpBackend;
// This is only used by setVersion which will always have a source that's an IDBDatabase.
m_source->idbDatabase()->setSetVersionTransaction(backend.get());
backend->setCallbacks(frontend.get());
m_transaction = backend;
IDBPendingTransactionMonitor::removePendingTransaction(m_transaction.get());
- scheduleEvent(IDBAny::create(frontend.release()), 0);
+ enqueueEvent(IDBSuccessEvent::create(m_source, IDBAny::create(frontend.release())));
}
void IDBRequest::onSuccess(PassRefPtr<SerializedScriptValue> serializedScriptValue)
{
- scheduleEvent(IDBAny::create(serializedScriptValue), 0);
+ enqueueEvent(IDBSuccessEvent::create(m_source, IDBAny::create(serializedScriptValue)));
}
ScriptExecutionContext* IDBRequest::scriptExecutionContext() const
return ActiveDOMObject::scriptExecutionContext();
}
-bool IDBRequest::canSuspend() const
+bool IDBRequest::dispatchEvent(PassRefPtr<Event> event)
{
- // IDBTransactions cannot be suspended at the moment. We therefore
- // disallow the back/forward cache for pages that use IndexedDatabase.
- return false;
-}
+ ASSERT(m_readyState < DONE);
+ m_readyState = DONE;
-EventTargetData* IDBRequest::eventTargetData()
-{
- return &m_eventTargetData;
-}
+ bool ret = EventTarget::dispatchEvent(event);
-EventTargetData* IDBRequest::ensureEventTargetData()
-{
- return &m_eventTargetData;
+ if (m_transaction)
+ m_transaction->didCompleteTaskEvents();
+
+ return ret;
}
-void IDBRequest::timerFired(Timer<IDBRequest>*)
+void IDBRequest::enqueueEvent(PassRefPtr<Event> event)
{
- ASSERT(m_selfRef);
- ASSERT(m_pendingEvents.size());
- // FIXME: We should handle the stop event and stop any timers when we see it. We can then assert here that scriptExecutionContext is non-null.
-
- // We need to keep self-referencing ourself, otherwise it's possible we'll be deleted.
- // But in some cases, suspend() could be called while we're dispatching an event, so we
- // need to make sure that resume() doesn't re-start the timer based on m_selfRef being set.
- RefPtr<IDBRequest> selfRef = m_selfRef.release();
-
- // readyStateReset can be called synchronously while we're dispatching the event.
- RefPtr<IDBTransactionBackendInterface> transaction = m_transaction;
- m_transaction.clear();
-
- Vector<PendingEvent> pendingEvents;
- pendingEvents.swap(m_pendingEvents);
- for (size_t i = 0; i < pendingEvents.size(); ++i) {
- // It's possible we've navigated in which case we'll crash.
- if (!scriptExecutionContext())
- return;
+ ASSERT(m_readyState < DONE);
+ if (!scriptExecutionContext())
+ return;
- if (pendingEvents[i].m_error) {
- ASSERT(!pendingEvents[i].m_result);
- dispatchEvent(IDBErrorEvent::create(m_source, *pendingEvents[i].m_error));
- } else {
- ASSERT(pendingEvents[i].m_result->type() != IDBAny::UndefinedType);
- dispatchEvent(IDBSuccessEvent::create(m_source, pendingEvents[i].m_result));
- }
- }
- if (transaction) {
- // Now that we processed all pending events, let the transaction monitor check if
- // it can commit the current transaction or if there's anything new pending.
- // FIXME: Handle the workers case.
- transaction->didCompleteTaskEvents();
- }
+ ASSERT(scriptExecutionContext()->isDocument());
+ EventQueue* eventQueue = static_cast<Document*>(scriptExecutionContext())->eventQueue();
+ event->setTarget(this);
+ eventQueue->enqueueEvent(event);
}
-void IDBRequest::scheduleEvent(PassRefPtr<IDBAny> result, PassRefPtr<IDBDatabaseError> error)
+EventTargetData* IDBRequest::eventTargetData()
{
- ASSERT(m_readyState < DONE);
- ASSERT(!!m_selfRef == m_timer.isActive());
-
- PendingEvent pendingEvent;
- pendingEvent.m_result = result;
- pendingEvent.m_error = error;
- m_pendingEvents.append(pendingEvent);
+ return &m_eventTargetData;
+}
- m_readyState = DONE;
- if (!m_timer.isActive()) {
- m_selfRef = this;
- m_timer.startOneShot(0);
- }
+EventTargetData* IDBRequest::ensureEventTargetData()
+{
+ return &m_eventTargetData;
}
} // namespace WebCore
#if ENABLE(INDEXED_DATABASE)
#include "ActiveDOMObject.h"
+#include "Event.h"
#include "EventListener.h"
#include "EventNames.h"
#include "EventTarget.h"
#include "IDBAny.h"
#include "IDBCallbacks.h"
-#include "Timer.h"
-#include <wtf/Vector.h>
namespace WebCore {
class IDBRequest : public IDBCallbacks, public EventTarget, public ActiveDOMObject {
public:
- static PassRefPtr<IDBRequest> create(ScriptExecutionContext* context, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface* transaction) { return adoptRef(new IDBRequest(context, source, transaction)); }
+ static PassRefPtr<IDBRequest> create(ScriptExecutionContext*, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface*);
virtual ~IDBRequest();
// Defined in the IDL
// EventTarget
virtual IDBRequest* toIDBRequest() { return this; }
-
- // ActiveDOMObject
virtual ScriptExecutionContext* scriptExecutionContext() const;
- virtual bool canSuspend() const;
+ virtual bool dispatchEvent(PassRefPtr<Event>);
+ bool dispatchEvent(PassRefPtr<Event> event, ExceptionCode& ec) { return EventTarget::dispatchEvent(event, ec); }
using ThreadSafeShared<IDBCallbacks>::ref;
using ThreadSafeShared<IDBCallbacks>::deref;
private:
IDBRequest(ScriptExecutionContext*, PassRefPtr<IDBAny> source, IDBTransactionBackendInterface* transaction);
- void timerFired(Timer<IDBRequest>*);
- void scheduleEvent(PassRefPtr<IDBAny> result, PassRefPtr<IDBDatabaseError>);
+ void enqueueEvent(PassRefPtr<Event>);
// EventTarget
virtual void refEventTarget() { ref(); }
RefPtr<IDBAny> m_source;
RefPtr<IDBTransactionBackendInterface> m_transaction;
- struct PendingEvent {
- RefPtr<IDBAny> m_result;
- RefPtr<IDBDatabaseError> m_error;
- };
- Vector<PendingEvent> m_pendingEvents;
-
- // Used to fire events asynchronously.
- Timer<IDBRequest> m_timer;
- RefPtr<IDBRequest> m_selfRef; // This is set to us iff there's an event pending.
-
ReadyState m_readyState;
+
EventTargetData m_eventTargetData;
};
#if ENABLE(INDEXED_DATABASE)
-#include "Event.h"
+#include "Document.h"
#include "EventException.h"
+#include "EventQueue.h"
#include "IDBAbortEvent.h"
#include "IDBCompleteEvent.h"
#include "IDBDatabase.h"
#include "IDBObjectStore.h"
#include "IDBObjectStoreBackendInterface.h"
#include "IDBPendingTransactionMonitor.h"
-#include "ScriptExecutionContext.h"
namespace WebCore {
+PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, PassRefPtr<IDBTransactionBackendInterface> backend, IDBDatabase* db)
+{
+ return adoptRef(new IDBTransaction(context, backend, db));
+}
+
IDBTransaction::IDBTransaction(ScriptExecutionContext* context, PassRefPtr<IDBTransactionBackendInterface> backend, IDBDatabase* db)
: ActiveDOMObject(context, this)
, m_backend(backend)
, m_database(db)
, m_mode(m_backend->mode())
- , m_onAbortTimer(this, &IDBTransaction::onAbortTimerFired)
- , m_onCompleteTimer(this, &IDBTransaction::onCompleteTimerFired)
{
IDBPendingTransactionMonitor::addPendingTransaction(m_backend.get());
}
PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionCode& ec)
{
+ // FIXME: It'd be better (because it's more deterministic) if we didn't start throwing this until the complete or abort event fires.
if (!m_backend) {
ec = IDBDatabaseException::NOT_ALLOWED_ERR;
return 0;
void IDBTransaction::abort()
{
+ RefPtr<IDBTransaction> selfRef = this;
if (m_backend)
m_backend->abort();
}
void IDBTransaction::onAbort()
{
- ASSERT(!m_onAbortTimer.isActive());
- ASSERT(!m_onCompleteTimer.isActive());
- m_selfRef = this;
- m_onAbortTimer.startOneShot(0);
- m_backend.clear(); // Release the backend as it holds a (circular) reference back to us.
+ enqueueEvent(IDBAbortEvent::create());
}
void IDBTransaction::onComplete()
{
- ASSERT(!m_onAbortTimer.isActive());
- ASSERT(!m_onCompleteTimer.isActive());
- m_selfRef = this;
- m_onCompleteTimer.startOneShot(0);
- m_backend.clear(); // Release the backend as it holds a (circular) reference back to us.
+ enqueueEvent(IDBCompleteEvent::create());
}
bool IDBTransaction::canSuspend() const
{
- // We may be in the middle of a transaction so we cannot suspend our object.
- // Instead, we simply don't allow the owner page to go into the back/forward cache.
- return false;
+ return !m_backend;
}
void IDBTransaction::contextDestroyed()
{
ActiveDOMObject::contextDestroyed();
+
+ RefPtr<IDBTransaction> selfRef = this;
if (m_backend)
m_backend->abort();
+ m_backend.clear();
}
-EventTargetData* IDBTransaction::eventTargetData()
+void IDBTransaction::enqueueEvent(PassRefPtr<Event> event)
{
- return &m_eventTargetData;
-}
+ if (!scriptExecutionContext())
+ return;
-EventTargetData* IDBTransaction::ensureEventTargetData()
-{
- return &m_eventTargetData;
-}
-
-void IDBTransaction::onAbortTimerFired(Timer<IDBTransaction>* transaction)
-{
- ASSERT(m_selfRef);
- RefPtr<IDBTransaction> selfRef = m_selfRef.release();
- dispatchEvent(IDBAbortEvent::create());
+ ASSERT(m_backend);
+ m_backend.clear();
+
+ ASSERT(scriptExecutionContext()->isDocument());
+ EventQueue* eventQueue = static_cast<Document*>(scriptExecutionContext())->eventQueue();
+ event->setTarget(this);
+ eventQueue->enqueueEvent(event);
}
-void IDBTransaction::onCompleteTimerFired(Timer<IDBTransaction>* transaction)
+EventTargetData* IDBTransaction::eventTargetData()
{
- ASSERT(m_selfRef);
- RefPtr<IDBTransaction> selfRef = m_selfRef.release();
- dispatchEvent(IDBCompleteEvent::create());
+ return &m_eventTargetData;
}
}
#include "EventTarget.h"
#include "IDBTransactionBackendInterface.h"
#include "IDBTransactionCallbacks.h"
-#include "Timer.h"
#include <wtf/RefCounted.h>
namespace WebCore {
class IDBTransaction : public IDBTransactionCallbacks, public EventTarget, public ActiveDOMObject {
public:
- static PassRefPtr<IDBTransaction> create(ScriptExecutionContext* context, PassRefPtr<IDBTransactionBackendInterface> backend, IDBDatabase* db)
- {
- return adoptRef(new IDBTransaction(context, backend, db));
- }
+ static PassRefPtr<IDBTransaction> create(ScriptExecutionContext*, PassRefPtr<IDBTransactionBackendInterface>, IDBDatabase*);
virtual ~IDBTransaction();
enum Mode {
// EventTarget
virtual IDBTransaction* toIDBTransaction() { return this; }
+ virtual ScriptExecutionContext* scriptExecutionContext() const;
// ActiveDOMObject
- virtual ScriptExecutionContext* scriptExecutionContext() const;
virtual bool canSuspend() const;
virtual void contextDestroyed();
private:
IDBTransaction(ScriptExecutionContext*, PassRefPtr<IDBTransactionBackendInterface>, IDBDatabase*);
+ void enqueueEvent(PassRefPtr<Event>);
+
// EventTarget
virtual void refEventTarget() { ref(); }
virtual void derefEventTarget() { deref(); }
virtual EventTargetData* eventTargetData();
virtual EventTargetData* ensureEventTargetData();
- void onAbortTimerFired(Timer<IDBTransaction>*);
- void onCompleteTimerFired(Timer<IDBTransaction>*);
-
- EventTargetData m_eventTargetData;
RefPtr<IDBTransactionBackendInterface> m_backend;
RefPtr<IDBDatabase> m_database;
unsigned short m_mode;
- Timer<IDBTransaction> m_onAbortTimer;
- Timer<IDBTransaction> m_onCompleteTimer;
- RefPtr<IDBTransaction> m_selfRef; // This is set to us iff there's an event pending.
+ EventTargetData m_eventTargetData;
};
} // namespace WebCore