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/ExecutionContext.h" 32 #include "core/events/EventQueue.h" 33 #include "core/inspector/ScriptCallStack.h" 34 #include "modules/indexeddb/IDBDatabase.h" 35 #include "modules/indexeddb/IDBEventDispatcher.h" 36 #include "modules/indexeddb/IDBIndex.h" 37 #include "modules/indexeddb/IDBObjectStore.h" 38 #include "modules/indexeddb/IDBOpenDBRequest.h" 39 #include "modules/indexeddb/IDBPendingTransactionMonitor.h" 40 #include "modules/indexeddb/IDBTracing.h" 41 42 using blink::WebIDBDatabase; 43 44 namespace WebCore { 45 46 IDBTransaction* IDBTransaction::create(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, blink::WebIDBTransactionMode mode, IDBDatabase* db) 47 { 48 IDBOpenDBRequest* openDBRequest = 0; 49 IDBTransaction* transaction = adoptRefCountedGarbageCollectedWillBeNoop(new IDBTransaction(context, id, objectStoreNames, mode, db, openDBRequest, IDBDatabaseMetadata())); 50 transaction->suspendIfNeeded(); 51 return transaction; 52 } 53 54 IDBTransaction* IDBTransaction::create(ExecutionContext* context, int64_t id, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 55 { 56 IDBTransaction* transaction = adoptRefCountedGarbageCollectedWillBeNoop(new IDBTransaction(context, id, Vector<String>(), blink::WebIDBTransactionModeVersionChange, db, openDBRequest, previousMetadata)); 57 transaction->suspendIfNeeded(); 58 return transaction; 59 } 60 61 const AtomicString& IDBTransaction::modeReadOnly() 62 { 63 DEFINE_STATIC_LOCAL(AtomicString, readonly, ("readonly", AtomicString::ConstructFromLiteral)); 64 return readonly; 65 } 66 67 const AtomicString& IDBTransaction::modeReadWrite() 68 { 69 DEFINE_STATIC_LOCAL(AtomicString, readwrite, ("readwrite", AtomicString::ConstructFromLiteral)); 70 return readwrite; 71 } 72 73 const AtomicString& IDBTransaction::modeVersionChange() 74 { 75 DEFINE_STATIC_LOCAL(AtomicString, versionchange, ("versionchange", AtomicString::ConstructFromLiteral)); 76 return versionchange; 77 } 78 79 IDBTransaction::IDBTransaction(ExecutionContext* context, int64_t id, const Vector<String>& objectStoreNames, blink::WebIDBTransactionMode mode, IDBDatabase* db, IDBOpenDBRequest* openDBRequest, const IDBDatabaseMetadata& previousMetadata) 80 : ActiveDOMObject(context) 81 , m_id(id) 82 , m_database(db) 83 , m_objectStoreNames(objectStoreNames) 84 , m_openDBRequest(openDBRequest) 85 , m_mode(mode) 86 , m_state(Active) 87 , m_hasPendingActivity(true) 88 , m_contextStopped(false) 89 , m_previousMetadata(previousMetadata) 90 { 91 ScriptWrappable::init(this); 92 if (mode == blink::WebIDBTransactionModeVersionChange) { 93 // Not active until the callback. 94 m_state = Inactive; 95 } 96 97 if (m_state == Active) 98 IDBPendingTransactionMonitor::from(*context).addNewTransaction(*this); 99 m_database->transactionCreated(this); 100 } 101 102 IDBTransaction::~IDBTransaction() 103 { 104 ASSERT(m_state == Finished || m_contextStopped); 105 ASSERT(m_requestList.isEmpty() || m_contextStopped); 106 } 107 108 void IDBTransaction::trace(Visitor* visitor) 109 { 110 visitor->trace(m_database); 111 visitor->trace(m_openDBRequest); 112 visitor->trace(m_error); 113 visitor->trace(m_requestList); 114 visitor->trace(m_objectStoreMap); 115 visitor->trace(m_deletedObjectStores); 116 visitor->trace(m_objectStoreCleanupMap); 117 EventTargetWithInlineData::trace(visitor); 118 } 119 120 const String& IDBTransaction::mode() const 121 { 122 return modeToString(m_mode); 123 } 124 125 void IDBTransaction::setError(PassRefPtrWillBeRawPtr<DOMError> error) 126 { 127 ASSERT(m_state != Finished); 128 ASSERT(error); 129 130 // The first error to be set is the true cause of the 131 // transaction abort. 132 if (!m_error) { 133 m_error = error; 134 } 135 } 136 137 IDBObjectStore* IDBTransaction::objectStore(const String& name, ExceptionState& exceptionState) 138 { 139 if (m_state == Finished) { 140 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage); 141 return 0; 142 } 143 144 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 145 if (it != m_objectStoreMap.end()) 146 return it->value; 147 148 if (!isVersionChange() && !m_objectStoreNames.contains(name)) { 149 exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage); 150 return 0; 151 } 152 153 int64_t objectStoreId = m_database->findObjectStoreId(name); 154 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 155 ASSERT(isVersionChange()); 156 exceptionState.throwDOMException(NotFoundError, IDBDatabase::noSuchObjectStoreErrorMessage); 157 return 0; 158 } 159 160 const IDBDatabaseMetadata& metadata = m_database->metadata(); 161 162 IDBObjectStore* objectStore = IDBObjectStore::create(metadata.objectStores.get(objectStoreId), this); 163 objectStoreCreated(name, objectStore); 164 return objectStore; 165 } 166 167 void IDBTransaction::objectStoreCreated(const String& name, IDBObjectStore* objectStore) 168 { 169 ASSERT(m_state != Finished); 170 m_objectStoreMap.set(name, objectStore); 171 if (isVersionChange()) 172 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 173 } 174 175 void IDBTransaction::objectStoreDeleted(const String& name) 176 { 177 ASSERT(m_state != Finished); 178 ASSERT(isVersionChange()); 179 IDBObjectStoreMap::iterator it = m_objectStoreMap.find(name); 180 if (it != m_objectStoreMap.end()) { 181 IDBObjectStore* objectStore = it->value; 182 m_objectStoreMap.remove(name); 183 objectStore->markDeleted(); 184 m_objectStoreCleanupMap.set(objectStore, objectStore->metadata()); 185 m_deletedObjectStores.add(objectStore); 186 } 187 } 188 189 void IDBTransaction::setActive(bool active) 190 { 191 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to setActive(%s)", active ? "true" : "false"); 192 if (m_state == Finishing) 193 return; 194 ASSERT(active != (m_state == Active)); 195 m_state = active ? Active : Inactive; 196 197 if (!active && m_requestList.isEmpty() && backendDB()) 198 backendDB()->commit(m_id); 199 } 200 201 void IDBTransaction::abort(ExceptionState& exceptionState) 202 { 203 if (m_state == Finishing || m_state == Finished) { 204 exceptionState.throwDOMException(InvalidStateError, IDBDatabase::transactionFinishedErrorMessage); 205 return; 206 } 207 208 m_state = Finishing; 209 210 if (m_contextStopped) 211 return; 212 213 while (!m_requestList.isEmpty()) { 214 IDBRequest* request = *m_requestList.begin(); 215 m_requestList.remove(request); 216 request->abort(); 217 } 218 219 if (backendDB()) 220 backendDB()->abort(m_id); 221 } 222 223 void IDBTransaction::registerRequest(IDBRequest* request) 224 { 225 ASSERT(request); 226 ASSERT(m_state == Active); 227 m_requestList.add(request); 228 } 229 230 void IDBTransaction::unregisterRequest(IDBRequest* request) 231 { 232 ASSERT(request); 233 // If we aborted the request, it will already have been removed. 234 m_requestList.remove(request); 235 } 236 237 void IDBTransaction::onAbort(PassRefPtrWillBeRawPtr<DOMError> prpError) 238 { 239 IDB_TRACE("IDBTransaction::onAbort"); 240 if (m_contextStopped) { 241 m_database->transactionFinished(this); 242 return; 243 } 244 245 RefPtrWillBeRawPtr<DOMError> error = prpError; 246 ASSERT(m_state != Finished); 247 248 if (m_state != Finishing) { 249 ASSERT(error.get()); 250 setError(error.release()); 251 252 // Abort was not triggered by front-end, so outstanding requests must 253 // be aborted now. 254 while (!m_requestList.isEmpty()) { 255 IDBRequest* request = *m_requestList.begin(); 256 m_requestList.remove(request); 257 request->abort(); 258 } 259 m_state = Finishing; 260 } 261 262 if (isVersionChange()) { 263 for (IDBObjectStoreMetadataMap::iterator it = m_objectStoreCleanupMap.begin(); it != m_objectStoreCleanupMap.end(); ++it) 264 it->key->setMetadata(it->value); 265 m_database->setMetadata(m_previousMetadata); 266 m_database->close(); 267 } 268 m_objectStoreCleanupMap.clear(); 269 270 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 271 enqueueEvent(Event::createBubble(EventTypeNames::abort)); 272 273 m_database->transactionFinished(this); 274 } 275 276 void IDBTransaction::onComplete() 277 { 278 IDB_TRACE("IDBTransaction::onComplete"); 279 if (m_contextStopped) { 280 m_database->transactionFinished(this); 281 return; 282 } 283 284 ASSERT(m_state != Finished); 285 m_state = Finishing; 286 m_objectStoreCleanupMap.clear(); 287 288 // Enqueue events before notifying database, as database may close which enqueues more events and order matters. 289 enqueueEvent(Event::create(EventTypeNames::complete)); 290 291 m_database->transactionFinished(this); 292 } 293 294 bool IDBTransaction::hasPendingActivity() const 295 { 296 // FIXME: In an ideal world, we should return true as long as anyone has a or can 297 // get a handle to us or any child request object and any of those have 298 // event listeners. This is in order to handle user generated events properly. 299 return m_hasPendingActivity && !m_contextStopped; 300 } 301 302 blink::WebIDBTransactionMode IDBTransaction::stringToMode(const String& modeString, ExceptionState& exceptionState) 303 { 304 if (modeString.isNull() 305 || modeString == IDBTransaction::modeReadOnly()) 306 return blink::WebIDBTransactionModeReadOnly; 307 if (modeString == IDBTransaction::modeReadWrite()) 308 return blink::WebIDBTransactionModeReadWrite; 309 310 exceptionState.throwTypeError("The mode provided ('" + modeString + "') is not one of 'readonly' or 'readwrite'."); 311 return blink::WebIDBTransactionModeReadOnly; 312 } 313 314 const AtomicString& IDBTransaction::modeToString(blink::WebIDBTransactionMode mode) 315 { 316 switch (mode) { 317 case blink::WebIDBTransactionModeReadOnly: 318 return IDBTransaction::modeReadOnly(); 319 break; 320 321 case blink::WebIDBTransactionModeReadWrite: 322 return IDBTransaction::modeReadWrite(); 323 break; 324 325 case blink::WebIDBTransactionModeVersionChange: 326 return IDBTransaction::modeVersionChange(); 327 break; 328 } 329 330 ASSERT_NOT_REACHED(); 331 return IDBTransaction::modeReadOnly(); 332 } 333 334 const AtomicString& IDBTransaction::interfaceName() const 335 { 336 return EventTargetNames::IDBTransaction; 337 } 338 339 ExecutionContext* IDBTransaction::executionContext() const 340 { 341 return ActiveDOMObject::executionContext(); 342 } 343 344 bool IDBTransaction::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event) 345 { 346 IDB_TRACE("IDBTransaction::dispatchEvent"); 347 if (m_contextStopped || !executionContext()) { 348 m_state = Finished; 349 return false; 350 } 351 ASSERT(m_state != Finished); 352 ASSERT(m_hasPendingActivity); 353 ASSERT(executionContext()); 354 ASSERT(event->target() == this); 355 m_state = Finished; 356 357 // Break reference cycles. 358 for (IDBObjectStoreMap::iterator it = m_objectStoreMap.begin(); it != m_objectStoreMap.end(); ++it) 359 it->value->transactionFinished(); 360 m_objectStoreMap.clear(); 361 for (IDBObjectStoreSet::iterator it = m_deletedObjectStores.begin(); it != m_deletedObjectStores.end(); ++it) 362 (*it)->transactionFinished(); 363 m_deletedObjectStores.clear(); 364 365 WillBeHeapVector<RefPtrWillBeMember<EventTarget> > targets; 366 targets.append(this); 367 targets.append(db()); 368 369 // FIXME: When we allow custom event dispatching, this will probably need to change. 370 ASSERT(event->type() == EventTypeNames::complete || event->type() == EventTypeNames::abort); 371 bool returnValue = IDBEventDispatcher::dispatch(event.get(), targets); 372 // FIXME: Try to construct a test where |this| outlives openDBRequest and we 373 // get a crash. 374 if (m_openDBRequest) { 375 ASSERT(isVersionChange()); 376 m_openDBRequest->transactionDidFinishAndDispatch(); 377 } 378 m_hasPendingActivity = false; 379 return returnValue; 380 } 381 382 void IDBTransaction::stop() 383 { 384 if (m_contextStopped) 385 return; 386 387 m_contextStopped = true; 388 389 abort(IGNORE_EXCEPTION); 390 } 391 392 void IDBTransaction::enqueueEvent(PassRefPtrWillBeRawPtr<Event> event) 393 { 394 ASSERT_WITH_MESSAGE(m_state != Finished, "A finished transaction tried to enqueue an event of type %s.", event->type().utf8().data()); 395 if (m_contextStopped || !executionContext()) 396 return; 397 398 EventQueue* eventQueue = executionContext()->eventQueue(); 399 event->setTarget(this); 400 eventQueue->enqueueEvent(event); 401 } 402 403 blink::WebIDBDatabase* IDBTransaction::backendDB() const 404 { 405 return m_database->backend(); 406 } 407 408 } // namespace WebCore 409