1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "IDBTransaction.h" 28 29#if ENABLE(INDEXED_DATABASE) 30 31#include "EventException.h" 32#include "EventQueue.h" 33#include "ExceptionCodePlaceholder.h" 34#include "IDBDatabase.h" 35#include "IDBDatabaseException.h" 36#include "IDBEventDispatcher.h" 37#include "IDBIndex.h" 38#include "IDBObjectStore.h" 39#include "IDBOpenDBRequest.h" 40#include "IDBPendingTransactionMonitor.h" 41#include "Logging.h" 42#include "ScriptExecutionContext.h" 43 44namespace WebCore { 45 46PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db) 47{ 48 IDBOpenDBRequest* openDBRequest = 0; 49 RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata()))); 50 transaction->suspendIfNeeded(); 51 return transaction.release(); 52} 53 54PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 55{ 56 RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, Vector<String>(), IndexedDB::TransactionMode::VersionChange, db, openDBRequest, previousMetadata))); 57 transaction->suspendIfNeeded(); 58 return transaction.release(); 59} 60 61const AtomicString& IDBTransaction::modeReadOnly() 62{ 63 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral)); 64 return readonly; 65} 66 67const AtomicString& IDBTransaction::modeReadWrite() 68{ 69 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral)); 70 return readwrite; 71} 72 73const AtomicString& IDBTransaction::modeVersionChange() 74{ 75 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral)); 76 return versionchange; 77} 78 79const AtomicString& IDBTransaction::modeReadOnlyLegacy() 80{ 81 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readonly, ("0", AtomicString::ConstructFromLiteral)); 82 return readonly; 83} 84 85const AtomicString& IDBTransaction::modeReadWriteLegacy() 86{ 87 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("1", AtomicString::ConstructFromLiteral)); 88 return readwrite; 89} 90 91 92IDBTransaction::IDBTransaction(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 93 : ActiveDOMObject(context) 94 , m_id(id) 95 , m_database(db) 96 , m_objectStoreNames(objectStoreNames) 97 , m_openDBRequest(openDBRequest) 98 , m_mode(mode) 99 , m_state(Active) 100 , m_hasPendingActivity(true) 101 , m_contextStopped(false) 102 , m_previousMetadata(previousMetadata) 103{ 104 if (mode == IndexedDB::TransactionMode::VersionChange) { 105 // Not active until the callback. 106 m_state = Inactive; 107 } 108 109 // We pass a reference of this object before it can be adopted. 110 relaxAdoptionRequirement(); 111 if (m_state == Active) 112 IDBPendingTransactionMonitor::addNewTransaction(this); 113 m_database->transactionCreated(this); 114} 115 116IDBTransaction::~IDBTransaction() 117{ 118 ASSERT(m_state == Finished || m_contextStopped); 119 ASSERT(m_requestList.isEmpty() || m_contextStopped); 120} 121 122const String& IDBTransaction::mode() const 123{ 124 return modeToString(m_mode); 125} 126 127void IDBTransaction::setError(PassRefPtr<DOMError> error, const String& errorMessage) 128{ 129 ASSERT(m_state != Finished); 130 ASSERT(error); 131 132 // The first error to be set is the true cause of the 133 // transaction abort. 134 if (!m_error) { 135 m_error = error; 136 m_errorMessage = errorMessage; 137 } 138} 139 140PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionCode& ec) 141{ 142 if (m_state == Finished) { 143 ec = IDBDatabaseException::InvalidStateError; 144 return 0; 145 } 146 147 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 148 if (it != m_objectStoreMap.end()) 149 return it->value; 150 151 if (!isVersionChange() && !m_objectStoreNames.contains(name)) { 152 ec = IDBDatabaseException::NotFoundError; 153 return 0; 154 } 155 156 int64_t objectStoreId = m_database->findObjectStoreId(name); 157 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 158 ASSERT(isVersionChange()); 159 ec = IDBDatabaseException::NotFoundError; 160 return 0; 161 } 162 163 const IDBDatabaseMetadata& metadata = m_database->metadata(); 164 165 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this); 166 objectStoreCreated(name, objectStore); 167 return objectStore.release(); 168} 169 170void IDBTransaction::objectStoreCreated(const String& name, PassRefPtr<IDBObjectStore> prpObjectStore) 171{ 172 ASSERT(m_state != Finished); 173 RefPtr<IDBObjectStore> objectStore = prpObjectStore; 174 m_objectStoreMap.set(name, objectStore); 175 if (isVersionChange()) 176 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 177} 178 179void IDBTransaction::objectStoreDeleted(const String& name) 180{ 181 ASSERT(m_state != Finished); 182 ASSERT(isVersionChange()); 183 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 184 if (it != m_objectStoreMap.end()) { 185 RefPtr<IDBObjectStore> objectStore = it->value; 186 m_objectStoreMap.remove(name); 187 objectStore->markDeleted(); 188 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 189 m_deletedObjectStores.add(objectStore); 190 } 191} 192 193void IDBTransaction::setActive(bool active) 194{ 195 LOG(StorageAPI, "IDBTransaction::setActive(%s) for transaction id %lli", active ? "true" : "false", static_cast<long long>(m_id)); 196 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false"); 197 if (m_state == Finishing) 198 return; 199 ASSERT(active != (m_state == Active)); 200 m_state = active ? Active : Inactive; 201 202 if (!active && m_requestList.isEmpty()) 203 backendDB()->commit(m_id); 204} 205 206void IDBTransaction::abort(ExceptionCode& ec) 207{ 208 if (m_state == Finishing || m_state == Finished) { 209 ec = IDBDatabaseException::InvalidStateError; 210 return; 211 } 212 213 m_state = Finishing; 214 215 while (!m_requestList.isEmpty()) { 216 RefPtr<IDBRequest> request = *m_requestList.begin(); 217 m_requestList.remove(request); 218 request->abort(); 219 } 220 221 RefPtr<IDBTransaction> selfRef = this; 222 backendDB()->abort(m_id); 223} 224 225IDBTransaction::OpenCursorNotifier::OpenCursorNotifier(PassRefPtr<IDBTransaction> transaction, IDBCursor* cursor) 226 : m_transaction(transaction), 227 m_cursor(cursor) 228{ 229 m_transaction->registerOpenCursor(m_cursor); 230} 231 232IDBTransaction::OpenCursorNotifier::~OpenCursorNotifier() 233{ 234 if (m_cursor) 235 m_transaction->unregisterOpenCursor(m_cursor); 236} 237 238void IDBTransaction::OpenCursorNotifier::cursorFinished() 239{ 240 if (m_cursor) { 241 m_transaction->unregisterOpenCursor(m_cursor); 242 m_cursor = 0; 243 m_transaction.clear(); 244 } 245} 246 247void IDBTransaction::registerOpenCursor(IDBCursor* cursor) 248{ 249 m_openCursors.add(cursor); 250} 251 252void IDBTransaction::unregisterOpenCursor(IDBCursor* cursor) 253{ 254 m_openCursors.remove(cursor); 255} 256 257void IDBTransaction::closeOpenCursors() 258{ 259 HashSet<IDBCursor*> cursors; 260 cursors.swap(m_openCursors); 261 for (HashSet<IDBCursor*>::iterator i = cursors.begin(); i != cursors.end(); ++i) 262 (*i)->close(); 263} 264 265void IDBTransaction::registerRequest(IDBRequest* request) 266{ 267 ASSERT(request); 268 ASSERT(m_state == Active); 269 m_requestList.add(request); 270} 271 272void IDBTransaction::unregisterRequest(IDBRequest* request) 273{ 274 ASSERT(request); 275 // If we aborted the request, it will already have been removed. 276 m_requestList.remove(request); 277} 278 279void IDBTransaction::onAbort(PassRefPtr<IDBDatabaseError> prpError) 280{ 281 LOG(StorageAPI, "IDBTransaction::onAbort"); 282 RefPtr<IDBDatabaseError> error = prpError; 283 ASSERT(m_state != Finished); 284 285 if (m_state != Finishing) { 286 ASSERT(error.get()); 287 setError(DOMError::create(error->name()), error->message()); 288 289 // Abort was not triggered by front-end, so outstanding requests must 290 // be aborted now. 291 while (!m_requestList.isEmpty()) { 292 RefPtr<IDBRequest> request = *m_requestList.begin(); 293 m_requestList.remove(request); 294 request->abort(); 295 } 296 m_state = Finishing; 297 } 298 299 if (isVersionChange()) { 300 for (IDBObjectStoreMetadataMap::iterator it = m_objectStoreCleanupMap.begin(); it != m_objectStoreCleanupMap.end(); ++it) 301 it->key->setMetadata(it->value); 302 m_database->setMetadata(m_previousMetadata); 303 m_database->close(); 304 } 305 m_objectStoreCleanupMap.clear(); 306 closeOpenCursors(); 307 308 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 309 enqueueEvent(Event::create(eventNames().abortEvent, true, false)); 310 m_database->transactionFinished(this); 311} 312 313void IDBTransaction::onComplete() 314{ 315 LOG(StorageAPI, "IDBTransaction::onComplete"); 316 ASSERT(m_state != Finished); 317 m_state = Finishing; 318 m_objectStoreCleanupMap.clear(); 319 closeOpenCursors(); 320 321 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 322 enqueueEvent(Event::create(eventNames().completeEvent, false, false)); 323 m_database->transactionFinished(this); 324} 325 326bool IDBTransaction::hasPendingActivity() const 327{ 328 // FIXME: In an ideal world, we should return true as long as anyone has a or can 329 // get a handle to us or any child request object and any of those have 330 // event listeners. This is in order to handle user generated events properly. 331 return m_hasPendingActivity && !m_contextStopped; 332} 333 334IndexedDB::TransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionCode& ec) 335{ 336 if (modeString.isNull() 337 || modeString == IDBTransaction::modeReadOnly()) 338 return IndexedDB::TransactionMode::ReadOnly; 339 if (modeString == IDBTransaction::modeReadWrite()) 340 return IndexedDB::TransactionMode::ReadWrite; 341 342 ec = TypeError; 343 return IndexedDB::TransactionMode::ReadOnly; 344} 345 346const AtomicString& IDBTransaction::modeToString(IndexedDB::TransactionMode mode) 347{ 348 switch (mode) { 349 case IndexedDB::TransactionMode::ReadOnly: 350 return IDBTransaction::modeReadOnly(); 351 352 case IndexedDB::TransactionMode::ReadWrite: 353 return IDBTransaction::modeReadWrite(); 354 355 case IndexedDB::TransactionMode::VersionChange: 356 return IDBTransaction::modeVersionChange(); 357 } 358 359 ASSERT_NOT_REACHED(); 360 return IDBTransaction::modeReadOnly(); 361} 362 363bool IDBTransaction::dispatchEvent(PassRefPtr<Event> event) 364{ 365 LOG(StorageAPI, "IDBTransaction::dispatchEvent"); 366 ASSERT(m_state != Finished); 367 ASSERT(m_hasPendingActivity); 368 ASSERT(scriptExecutionContext()); 369 ASSERT(event->target() == this); 370 m_state = Finished; 371 372 // Break reference cycles. 373 for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it) 374 it->value->transactionFinished(); 375 m_objectStoreMap.clear(); 376 for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it) 377 (*it)->transactionFinished(); 378 m_deletedObjectStores.clear(); 379 380 Vector<RefPtr<EventTarget>> targets; 381 targets.append(this); 382 targets.append(db()); 383 384 // FIXME: When we allow custom event dispatching, this will probably need to change. 385 ASSERT(event->type() == eventNames().completeEvent || event->type() == eventNames().abortEvent); 386 bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets); 387 // FIXME: Try to construct a test where |this| outlives openDBRequest and we 388 // get a crash. 389 if (m_openDBRequest) { 390 ASSERT(isVersionChange()); 391 m_openDBRequest->transactionDidFinishAndDispatch(); 392 } 393 m_hasPendingActivity = false; 394 return returnValue; 395} 396 397bool IDBTransaction::canSuspend() const 398{ 399 // FIXME: Technically we can suspend before the first request is schedule 400 // and after the complete/abort event is enqueued. 401 return m_state == Finished; 402} 403 404void IDBTransaction::stop() 405{ 406 m_contextStopped = true; 407 408 abort(IGNORE_EXCEPTION); 409} 410 411void IDBTransaction::enqueueEvent(PassRefPtr<Event> event) 412{ 413 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().string().utf8().data()); 414 if (m_contextStopped || !scriptExecutionContext()) 415 return; 416 417 event->setTarget(this); 418 scriptExecutionContext()->eventQueue().enqueueEvent(event); 419} 420 421IDBDatabaseBackend* IDBTransaction::backendDB() const 422{ 423 return db()->backend(); 424} 425 426} 427 428#endif // ENABLE(INDEXED_DATABASE) 429