blob: 709f09d2af09898b63c804cfc3a359350e74b646 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "modules/indexeddb/IDBTransaction.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "bindings/core/v8/V8PerIsolateData.h"
#include "core/dom/DOMException.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/ExecutionContext.h"
#include "core/events/EventQueue.h"
#include "modules/IndexedDBNames.h"
#include "modules/indexeddb/IDBDatabase.h"
#include "modules/indexeddb/IDBEventDispatcher.h"
#include "modules/indexeddb/IDBIndex.h"
#include "modules/indexeddb/IDBObjectStore.h"
#include "modules/indexeddb/IDBOpenDBRequest.h"
#include "modules/indexeddb/IDBTracing.h"
#include "wtf/PtrUtil.h"
#include <memory>
using blink::WebIDBDatabase;
namespace blink {
IDBTransaction* IDBTransaction::createNonVersionChange(
ScriptState* scriptState,
int64_t id,
const HashSet<String>& scope,
WebIDBTransactionMode mode,
IDBDatabase* db) {
DCHECK_NE(mode, WebIDBTransactionModeVersionChange);
DCHECK(!scope.isEmpty()) << "Non-version transactions should operate on a "
"well-defined set of stores";
IDBOpenDBRequest* openDBRequest = nullptr;
IDBTransaction* transaction = new IDBTransaction(
scriptState, id, scope, mode, db, openDBRequest, IDBDatabaseMetadata());
transaction->suspendIfNeeded();
return transaction;
}
IDBTransaction* IDBTransaction::createVersionChange(
ScriptState* scriptState,
int64_t id,
IDBDatabase* db,
IDBOpenDBRequest* openDBRequest,
const IDBDatabaseMetadata& oldMetadata) {
IDBTransaction* transaction = new IDBTransaction(
scriptState, id, HashSet<String>(), WebIDBTransactionModeVersionChange,
db, openDBRequest, oldMetadata);
transaction->suspendIfNeeded();
return transaction;
}
namespace {
class DeactivateTransactionTask : public V8PerIsolateData::EndOfScopeTask {
public:
static std::unique_ptr<DeactivateTransactionTask> create(
IDBTransaction* transaction) {
return wrapUnique(new DeactivateTransactionTask(transaction));
}
void run() override {
m_transaction->setActive(false);
m_transaction.clear();
}
private:
explicit DeactivateTransactionTask(IDBTransaction* transaction)
: m_transaction(transaction) {}
Persistent<IDBTransaction> m_transaction;
};
} // namespace
IDBTransaction::IDBTransaction(ScriptState* scriptState,
int64_t id,
const HashSet<String>& scope,
WebIDBTransactionMode mode,
IDBDatabase* db,
IDBOpenDBRequest* openDBRequest,
const IDBDatabaseMetadata& oldMetadata)
: ActiveScriptWrappable(this),
ActiveDOMObject(scriptState->getExecutionContext()),
m_id(id),
m_database(db),
m_openDBRequest(openDBRequest),
m_mode(mode),
m_scope(scope),
m_oldDatabaseMetadata(oldMetadata) {
DCHECK(m_database);
if (isVersionChange()) {
DCHECK(scope.isEmpty());
// Not active until the callback.
m_state = Inactive;
} else {
DCHECK(!scope.isEmpty()) << "Non-versionchange transactions must operate "
"on a well-defined set of stores";
DCHECK(m_mode == WebIDBTransactionModeReadOnly ||
m_mode == WebIDBTransactionModeReadWrite)
<< "Invalid transaction mode";
}
if (m_state == Active)
V8PerIsolateData::from(scriptState->isolate())
->addEndOfScopeTask(DeactivateTransactionTask::create(this));
m_database->transactionCreated(this);
}
IDBTransaction::~IDBTransaction() {
DCHECK(m_state == Finished || m_contextStopped);
DCHECK(m_requestList.isEmpty() || m_contextStopped);
}
DEFINE_TRACE(IDBTransaction) {
visitor->trace(m_database);
visitor->trace(m_openDBRequest);
visitor->trace(m_error);
visitor->trace(m_requestList);
visitor->trace(m_objectStoreMap);
visitor->trace(m_createdObjectStores);
visitor->trace(m_deletedObjectStores);
visitor->trace(m_objectStoreCleanupMap);
EventTargetWithInlineData::trace(visitor);
ActiveDOMObject::trace(visitor);
}
void IDBTransaction::setError(DOMException* error) {
DCHECK_NE(m_state, Finished);
DCHECK(error);
// The first error to be set is the true cause of the
// transaction abort.
if (!m_error) {
m_error = error;
}
}
IDBObjectStore* IDBTransaction::objectStore(const String& name,
ExceptionState& exceptionState) {
if (isFinished()) {
exceptionState.throwDOMException(
InvalidStateError, IDBDatabase::transactionFinishedErrorMessage);
return nullptr;
}
IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
if (it != m_objectStoreMap.end())
return it->value;
if (!isVersionChange() && !m_scope.contains(name)) {
exceptionState.throwDOMException(
NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage);
return nullptr;
}
int64_t objectStoreId = m_database->findObjectStoreId(name);
if (objectStoreId == IDBObjectStoreMetadata::InvalidId) {
DCHECK(isVersionChange());
exceptionState.throwDOMException(
NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage);
return nullptr;
}
DCHECK(m_database->metadata().objectStores.contains(objectStoreId));
const IDBObjectStoreMetadata& objectStoreMetadata =
m_database->metadata().objectStores.get(objectStoreId);
IDBObjectStore* objectStore =
IDBObjectStore::create(objectStoreMetadata, this);
DCHECK(!m_objectStoreMap.contains(name));
m_objectStoreMap.set(name, objectStore);
m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
return objectStore;
}
void IDBTransaction::objectStoreCreated(const String& name,
IDBObjectStore* objectStore) {
DCHECK_NE(m_state, Finished)
<< "A finished transaction created an object store";
DCHECK_EQ(m_mode, WebIDBTransactionModeVersionChange)
<< "A non-versionchange transaction created an object store";
DCHECK(!m_objectStoreMap.contains(name))
<< "An object store was created with the name of an existing store";
m_objectStoreMap.set(name, objectStore);
m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
m_createdObjectStores.add(objectStore);
}
void IDBTransaction::objectStoreDeleted(const String& name) {
DCHECK_NE(m_state, Finished)
<< "A finished transaction deleted an object store";
DCHECK_EQ(m_mode, WebIDBTransactionModeVersionChange)
<< "A non-versionchange transaction deleted an object store";
IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name);
if (it != m_objectStoreMap.end()) {
IDBObjectStore* objectStore = it->value;
m_objectStoreMap.remove(name);
objectStore->markDeleted();
m_objectStoreCleanupMap.set(objectStore, objectStore->metadata());
m_deletedObjectStores.add(objectStore);
}
}
void IDBTransaction::objectStoreRenamed(const String& oldName,
const String& newName) {
DCHECK_NE(m_state, Finished)
<< "A finished transaction renamed an object store";
DCHECK_EQ(m_mode, WebIDBTransactionModeVersionChange)
<< "A non-versionchange transaction renamed an object store";
DCHECK(!m_objectStoreMap.contains(newName));
DCHECK(m_objectStoreMap.contains(oldName))
<< "The object store had to be accessed in order to be renamed.";
m_objectStoreMap.set(newName, m_objectStoreMap.take(oldName));
}
void IDBTransaction::setActive(bool active) {
DCHECK_NE(m_state, Finished) << "A finished transaction tried to setActive("
<< (active ? "true" : "false") << ")";
if (m_state == Finishing)
return;
DCHECK_NE(active, (m_state == Active));
m_state = active ? Active : Inactive;
if (!active && m_requestList.isEmpty() && backendDB())
backendDB()->commit(m_id);
}
void IDBTransaction::abort(ExceptionState& exceptionState) {
if (m_state == Finishing || m_state == Finished) {
exceptionState.throwDOMException(
InvalidStateError, IDBDatabase::transactionFinishedErrorMessage);
return;
}
m_state = Finishing;
if (m_contextStopped)
return;
abortOutstandingRequests();
revertDatabaseMetadata();
if (backendDB())
backendDB()->abort(m_id);
}
void IDBTransaction::registerRequest(IDBRequest* request) {
DCHECK(request);
DCHECK_EQ(m_state, Active);
m_requestList.add(request);
}
void IDBTransaction::unregisterRequest(IDBRequest* request) {
DCHECK(request);
// If we aborted the request, it will already have been removed.
m_requestList.remove(request);
}
void IDBTransaction::onAbort(DOMException* error) {
IDB_TRACE("IDBTransaction::onAbort");
if (m_contextStopped) {
finished();
return;
}
DCHECK_NE(m_state, Finished);
if (m_state != Finishing) {
// Abort was not triggered by front-end.
DCHECK(error);
setError(error);
abortOutstandingRequests();
revertDatabaseMetadata();
m_state = Finishing;
}
if (isVersionChange())
m_database->close();
// Enqueue events before notifying database, as database may close which
// enqueues more events and order matters.
enqueueEvent(Event::createBubble(EventTypeNames::abort));
finished();
}
void IDBTransaction::onComplete() {
IDB_TRACE("IDBTransaction::onComplete");
if (m_contextStopped) {
finished();
return;
}
DCHECK_NE(m_state, Finished);
m_state = Finishing;
// Enqueue events before notifying database, as database may close which
// enqueues more events and order matters.
enqueueEvent(Event::create(EventTypeNames::complete));
finished();
}
bool IDBTransaction::hasPendingActivity() const {
// FIXME: In an ideal world, we should return true as long as anyone has a or
// can get a handle to us or any child request object and any of those have
// event listeners. This is in order to handle user generated events
// properly.
return m_hasPendingActivity && !m_contextStopped;
}
WebIDBTransactionMode IDBTransaction::stringToMode(const String& modeString) {
if (modeString == IndexedDBNames::readonly)
return WebIDBTransactionModeReadOnly;
if (modeString == IndexedDBNames::readwrite)
return WebIDBTransactionModeReadWrite;
if (modeString == IndexedDBNames::versionchange)
return WebIDBTransactionModeVersionChange;
NOTREACHED();
return WebIDBTransactionModeReadOnly;
}
const String& IDBTransaction::mode() const {
switch (m_mode) {
case WebIDBTransactionModeReadOnly:
return IndexedDBNames::readonly;
case WebIDBTransactionModeReadWrite:
return IndexedDBNames::readwrite;
case WebIDBTransactionModeVersionChange:
return IndexedDBNames::versionchange;
}
NOTREACHED();
return IndexedDBNames::readonly;
}
WebIDBDatabase* IDBTransaction::backendDB() const {
return m_database->backend();
}
DOMStringList* IDBTransaction::objectStoreNames() const {
if (isVersionChange())
return m_database->objectStoreNames();
DOMStringList* objectStoreNames =
DOMStringList::create(DOMStringList::IndexedDB);
for (const String& objectStoreName : m_scope)
objectStoreNames->append(objectStoreName);
objectStoreNames->sort();
return objectStoreNames;
}
const AtomicString& IDBTransaction::interfaceName() const {
return EventTargetNames::IDBTransaction;
}
ExecutionContext* IDBTransaction::getExecutionContext() const {
return ActiveDOMObject::getExecutionContext();
}
DispatchEventResult IDBTransaction::dispatchEventInternal(Event* event) {
IDB_TRACE("IDBTransaction::dispatchEvent");
if (m_contextStopped || !getExecutionContext()) {
m_state = Finished;
return DispatchEventResult::CanceledBeforeDispatch;
}
DCHECK_NE(m_state, Finished);
DCHECK(m_hasPendingActivity);
DCHECK(getExecutionContext());
DCHECK_EQ(event->target(), this);
m_state = Finished;
HeapVector<Member<EventTarget>> targets;
targets.append(this);
targets.append(db());
// FIXME: When we allow custom event dispatching, this will probably need to
// change.
DCHECK(event->type() == EventTypeNames::complete ||
event->type() == EventTypeNames::abort);
DispatchEventResult dispatchResult =
IDBEventDispatcher::dispatch(event, targets);
// FIXME: Try to construct a test where |this| outlives openDBRequest and we
// get a crash.
if (m_openDBRequest) {
DCHECK(isVersionChange());
m_openDBRequest->transactionDidFinishAndDispatch();
}
m_hasPendingActivity = false;
return dispatchResult;
}
void IDBTransaction::contextDestroyed() {
if (m_contextStopped)
return;
m_contextStopped = true;
abort(IGNORE_EXCEPTION);
}
void IDBTransaction::enqueueEvent(Event* event) {
DCHECK_NE(m_state, Finished)
<< "A finished transaction tried to enqueue an event of type "
<< event->type() << ".";
if (m_contextStopped || !getExecutionContext())
return;
EventQueue* eventQueue = getExecutionContext()->getEventQueue();
event->setTarget(this);
eventQueue->enqueueEvent(event);
}
void IDBTransaction::abortOutstandingRequests() {
for (IDBRequest* request : m_requestList)
request->abort();
m_requestList.clear();
}
void IDBTransaction::revertDatabaseMetadata() {
DCHECK_NE(m_state, Active);
if (!isVersionChange())
return;
// Newly created stores must be marked as deleted.
for (IDBObjectStore* store : m_createdObjectStores) {
store->abort();
store->markDeleted();
}
// Used stores may need to mark indexes as deleted.
for (auto& it : m_objectStoreCleanupMap) {
it.key->abort();
it.key->setMetadata(it.value);
}
m_database->setMetadata(m_oldDatabaseMetadata);
}
void IDBTransaction::finished() {
#if DCHECK_IS_ON()
DCHECK(!m_finishCalled);
m_finishCalled = true;
#endif // DCHECK_IS_ON()
m_database->transactionFinished(this);
// Break reference cycles.
// TODO(jsbell): This can be removed c/o Oilpan.
for (auto& it : m_objectStoreMap)
it.value->transactionFinished();
m_objectStoreMap.clear();
for (auto& it : m_deletedObjectStores)
it->transactionFinished();
m_createdObjectStores.clear();
m_deletedObjectStores.clear();
m_objectStoreCleanupMap.clear();
}
} // namespace blink