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 "modules/indexeddb/IDBTransaction.h" 28 29 #include "bindings/v8/ExceptionState.h" 30 #include "bindings/v8/ExceptionStatePlaceholder.h" 31 #include "core/dom/DOMError.h" 32 #include "core/dom/EventQueue.h" 33 #include "core/dom/ScriptExecutionContext.h" 34 #include "core/inspector/ScriptCallStack.h" 35 #include "modules/indexeddb/IDBDatabase.h" 36 #include "modules/indexeddb/IDBEventDispatcher.h" 37 #include "modules/indexeddb/IDBIndex.h" 38 #include "modules/indexeddb/IDBObjectStore.h" 39 #include "modules/indexeddb/IDBOpenDBRequest.h" 40 #include "modules/indexeddb/IDBPendingTransactionMonitor.h" 41 #include "modules/indexeddb/IDBTracing.h" 42 43 namespace WebCore { 44 45 PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db) 46 { 47 IDBOpenDBRequest* openDBRequest = 0; 48 RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata()))); 49 transaction->suspendIfNeeded(); 50 return transaction.release(); 51 } 52 53 PassRefPtr<IDBTransaction> IDBTransaction::create(ScriptExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 54 { 55 RefPtr<IDBTransaction> transaction(adoptRef(new IDBTransaction(context, id, Vector<String>(), IndexedDB::TransactionVersionChange, db, openDBRequest, previousMetadata))); 56 transaction->suspendIfNeeded(); 57 return transaction.release(); 58 } 59 60 const AtomicString& IDBTransaction::modeReadOnly() 61 { 62 DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral)); 63 return readonly; 64 } 65 66 const AtomicString& IDBTransaction::modeReadWrite() 67 { 68 DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral)); 69 return readwrite; 70 } 71 72 const AtomicString& IDBTransaction::modeVersionChange() 73 { 74 DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral)); 75 return versionchange; 76 } 77 78 const AtomicString& IDBTransaction::modeReadOnlyLegacy() 79 { 80 DEFINE_STATIC_LOCAL(AtomicString, readonly, ("0", AtomicString::ConstructFromLiteral)); 81 return readonly; 82 } 83 84 const AtomicString& IDBTransaction::modeReadWriteLegacy() 85 { 86 DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("1", AtomicString::ConstructFromLiteral)); 87 return readwrite; 88 } 89 90 91 IDBTransaction::IDBTransaction(ScriptExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, IndexedDB::TransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 92 : ActiveDOMObject(context) 93 , m_id(id) 94 , m_database(db) 95 , m_objectStoreNames(objectStoreNames) 96 , m_openDBRequest(openDBRequest) 97 , m_mode(mode) 98 , m_state(Active) 99 , m_hasPendingActivity(true) 100 , m_contextStopped(false) 101 , m_previousMetadata(previousMetadata) 102 { 103 ScriptWrappable::init(this); 104 if (mode == IndexedDB::TransactionVersionChange) { 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 116 IDBTransaction::~IDBTransaction() 117 { 118 ASSERT(m_state == Finished || m_contextStopped); 119 ASSERT(m_requestList.isEmpty() || m_contextStopped); 120 } 121 122 const String& IDBTransaction::mode() const 123 { 124 return modeToString(m_mode); 125 } 126 127 void IDBTransaction::setError(PassRefPtr<DOMError> error) 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 } 137 } 138 139 PassRefPtr<IDBObjectStore> IDBTransaction::objectStore(const String& name, ExceptionState& es) 140 { 141 if (m_state == Finished) { 142 es.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage); 143 return 0; 144 } 145 146 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 147 if (it != m_objectStoreMap.end()) 148 return it->value; 149 150 if (!isVersionChange() && !m_objectStoreNames.contains(name)) { 151 es.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage); 152 return 0; 153 } 154 155 int64_t objectStoreId = m_database->findObjectStoreId(name); 156 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 157 ASSERT(isVersionChange()); 158 es.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage); 159 return 0; 160 } 161 162 const IDBDatabaseMetadata& metadata = m_database->metadata(); 163 164 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this); 165 objectStoreCreated(name, objectStore); 166 return objectStore.release(); 167 } 168 169 void IDBTransaction::objectStoreCreated(const String& name, PassRefPtr<IDBObjectStore> prpObjectStore) 170 { 171 ASSERT(m_state != Finished); 172 RefPtr<IDBObjectStore> objectStore = prpObjectStore; 173 m_objectStoreMap.set(name, objectStore); 174 if (isVersionChange()) 175 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 176 } 177 178 void IDBTransaction::objectStoreDeleted(const String& name) 179 { 180 ASSERT(m_state != Finished); 181 ASSERT(isVersionChange()); 182 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 183 if (it != m_objectStoreMap.end()) { 184 RefPtr<IDBObjectStore> objectStore = it->value; 185 m_objectStoreMap.remove(name); 186 objectStore->markDeleted(); 187 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 188 m_deletedObjectStores.add(objectStore); 189 } 190 } 191 192 void IDBTransaction::setActive(bool active) 193 { 194 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false"); 195 if (m_state == Finishing) 196 return; 197 ASSERT(active != (m_state == Active)); 198 m_state = active ? Active : Inactive; 199 200 if (!active && m_requestList.isEmpty()) 201 backendDB()->commit(m_id); 202 } 203 204 void IDBTransaction::abort(ExceptionState& es) 205 { 206 if (m_state == Finishing || m_state == Finished) { 207 es.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage); 208 return; 209 } 210 211 m_state = Finishing; 212 213 if (!m_contextStopped) { 214 while (!m_requestList.isEmpty()) { 215 RefPtr<IDBRequest> request = *m_requestList.begin(); 216 m_requestList.remove(request); 217 request->abort(); 218 } 219 } 220 221 RefPtr<IDBTransaction> selfRef = this; 222 backendDB()->abort(m_id); 223 } 224 225 IDBTransaction::OpenCursorNotifier::OpenCursorNotifier(PassRefPtr<IDBTransaction> transaction, IDBCursor* cursor) 226 : m_transaction(transaction), 227 m_cursor(cursor) 228 { 229 m_transaction->registerOpenCursor(m_cursor); 230 } 231 232 IDBTransaction::OpenCursorNotifier::~OpenCursorNotifier() 233 { 234 if (m_cursor) 235 m_transaction->unregisterOpenCursor(m_cursor); 236 } 237 238 void 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 247 void IDBTransaction::registerOpenCursor(IDBCursor* cursor) 248 { 249 m_openCursors.add(cursor); 250 } 251 252 void IDBTransaction::unregisterOpenCursor(IDBCursor* cursor) 253 { 254 m_openCursors.remove(cursor); 255 } 256 257 void 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 265 void IDBTransaction::registerRequest(IDBRequest* request) 266 { 267 ASSERT(request); 268 ASSERT(m_state == Active); 269 m_requestList.add(request); 270 } 271 272 void 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 279 void IDBTransaction::onAbort(PassRefPtr<DOMError> prpError) 280 { 281 IDB_TRACE("IDBTransaction::onAbort"); 282 RefPtr<DOMError> error = prpError; 283 ASSERT(m_state != Finished); 284 285 if (m_state != Finishing) { 286 ASSERT(error.get()); 287 setError(error.release()); 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 311 // If script has stopped and GC has completed, database may have last reference to this object. 312 RefPtr<IDBTransaction> protect(this); 313 m_database->transactionFinished(this); 314 } 315 316 void IDBTransaction::onComplete() 317 { 318 IDB_TRACE("IDBTransaction::onComplete"); 319 ASSERT(m_state != Finished); 320 m_state = Finishing; 321 m_objectStoreCleanupMap.clear(); 322 closeOpenCursors(); 323 324 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 325 enqueueEvent(Event::create(eventNames().completeEvent, false, false)); 326 327 // If script has stopped and GC has completed, database may have last reference to this object. 328 RefPtr<IDBTransaction> protect(this); 329 m_database->transactionFinished(this); 330 } 331 332 bool IDBTransaction::hasPendingActivity() const 333 { 334 // FIXME: In an ideal world, we should return true as long as anyone has a or can 335 // get a handle to us or any child request object and any of those have 336 // event listeners. This is in order to handle user generated events properly. 337 return m_hasPendingActivity && !m_contextStopped; 338 } 339 340 IndexedDB::TransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionState& es) 341 { 342 if (modeString.isNull() 343 || modeString == IDBTransaction::modeReadOnly()) 344 return IndexedDB::TransactionReadOnly; 345 if (modeString == IDBTransaction::modeReadWrite()) 346 return IndexedDB::TransactionReadWrite; 347 348 es.throwTypeError(); 349 return IndexedDB::TransactionReadOnly; 350 } 351 352 const AtomicString& IDBTransaction::modeToString(IndexedDB::TransactionMode mode) 353 { 354 switch (mode) { 355 case IndexedDB::TransactionReadOnly: 356 return IDBTransaction::modeReadOnly(); 357 break; 358 359 case IndexedDB::TransactionReadWrite: 360 return IDBTransaction::modeReadWrite(); 361 break; 362 363 case IndexedDB::TransactionVersionChange: 364 return IDBTransaction::modeVersionChange(); 365 break; 366 } 367 368 ASSERT_NOT_REACHED(); 369 return IDBTransaction::modeReadOnly(); 370 } 371 372 const AtomicString& IDBTransaction::interfaceName() const 373 { 374 return eventNames().interfaceForIDBTransaction; 375 } 376 377 ScriptExecutionContext* IDBTransaction::scriptExecutionContext() const 378 { 379 return ActiveDOMObject::scriptExecutionContext(); 380 } 381 382 bool IDBTransaction::dispatchEvent(PassRefPtr<Event> event) 383 { 384 IDB_TRACE("IDBTransaction::dispatchEvent"); 385 ASSERT(m_state != Finished); 386 ASSERT(m_hasPendingActivity); 387 ASSERT(scriptExecutionContext()); 388 ASSERT(event->target() == this); 389 m_state = Finished; 390 391 // Break reference cycles. 392 for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it) 393 it->value->transactionFinished(); 394 m_objectStoreMap.clear(); 395 for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it) 396 (*it)->transactionFinished(); 397 m_deletedObjectStores.clear(); 398 399 Vector<RefPtr<EventTarget> > targets; 400 targets.append(this); 401 targets.append(db()); 402 403 // FIXME: When we allow custom event dispatching, this will probably need to change. 404 ASSERT(event->type() == eventNames().completeEvent || event->type() == eventNames().abortEvent); 405 bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets); 406 // FIXME: Try to construct a test where |this| outlives openDBRequest and we 407 // get a crash. 408 if (m_openDBRequest) { 409 ASSERT(isVersionChange()); 410 m_openDBRequest->transactionDidFinishAndDispatch(); 411 } 412 m_hasPendingActivity = false; 413 return returnValue; 414 } 415 416 bool IDBTransaction::canSuspend() const 417 { 418 // FIXME: Technically we can suspend before the first request is schedule 419 // and after the complete/abort event is enqueued. 420 return m_state == Finished; 421 } 422 423 void IDBTransaction::stop() 424 { 425 if (m_contextStopped) 426 return; 427 428 m_contextStopped = true; 429 430 abort(IGNORE_EXCEPTION); 431 } 432 433 void IDBTransaction::enqueueEvent(PassRefPtr<Event> event) 434 { 435 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().string().utf8().data()); 436 if (m_contextStopped || !scriptExecutionContext()) 437 return; 438 439 EventQueue* eventQueue = scriptExecutionContext()->eventQueue(); 440 event->setTarget(this); 441 eventQueue->enqueueEvent(event); 442 } 443 444 EventTargetData* IDBTransaction::eventTargetData() 445 { 446 return &m_eventTargetData; 447 } 448 449 EventTargetData* IDBTransaction::ensureEventTargetData() 450 { 451 return &m_eventTargetData; 452 } 453 454 IDBDatabaseBackendInterface* IDBTransaction::backendDB() const 455 { 456 return db()->backend(); 457 } 458 459 } // namespace WebCore 460