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/IDBDatabase.h" 28 29 #include "bindings/v8/ExceptionState.h" 30 #include "bindings/v8/ExceptionStatePlaceholder.h" 31 #include "core/dom/DOMStringList.h" 32 #include "core/dom/EventQueue.h" 33 #include "core/dom/ScriptExecutionContext.h" 34 #include "core/inspector/ScriptCallStack.h" 35 #include "core/platform/HistogramSupport.h" 36 #include "modules/indexeddb/IDBAny.h" 37 #include "modules/indexeddb/IDBDatabaseCallbacks.h" 38 #include "modules/indexeddb/IDBEventDispatcher.h" 39 #include "modules/indexeddb/IDBHistograms.h" 40 #include "modules/indexeddb/IDBIndex.h" 41 #include "modules/indexeddb/IDBKeyPath.h" 42 #include "modules/indexeddb/IDBObjectStore.h" 43 #include "modules/indexeddb/IDBTracing.h" 44 #include "modules/indexeddb/IDBTransaction.h" 45 #include "modules/indexeddb/IDBVersionChangeEvent.h" 46 #include <limits> 47 #include "wtf/Atomics.h" 48 49 namespace WebCore { 50 51 const char IDBDatabase::indexDeletedErrorMessage[] = "The index or its object store has been deleted."; 52 const char IDBDatabase::isKeyCursorErrorMessage[] = "The cursor is a key cursor."; 53 const char IDBDatabase::noKeyOrKeyRangeErrorMessage[] = "No key or key range specified."; 54 const char IDBDatabase::noSuchIndexErrorMessage[] = "The specified index was not found."; 55 const char IDBDatabase::noSuchObjectStoreErrorMessage[] = "The specified object store was not found."; 56 const char IDBDatabase::noValueErrorMessage[] = "The cursor is being iterated or has iterated past its end."; 57 const char IDBDatabase::notValidKeyErrorMessage[] = "The parameter is not a valid key."; 58 const char IDBDatabase::notVersionChangeTransactionErrorMessage[] = "The database is not running a version change transaction."; 59 const char IDBDatabase::objectStoreDeletedErrorMessage[] = "The object store has been deleted."; 60 const char IDBDatabase::requestNotFinishedErrorMessage[] = "The request has not finished."; 61 const char IDBDatabase::sourceDeletedErrorMessage[] = "The cursor's source or effective object store has been deleted."; 62 const char IDBDatabase::transactionInactiveErrorMessage[] = "The transaction is not active."; 63 const char IDBDatabase::transactionFinishedErrorMessage[] = "The transaction has finished."; 64 65 PassRefPtr<IDBDatabase> IDBDatabase::create(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> database, PassRefPtr<IDBDatabaseCallbacks> callbacks) 66 { 67 RefPtr<IDBDatabase> idbDatabase(adoptRef(new IDBDatabase(context, database, callbacks))); 68 idbDatabase->suspendIfNeeded(); 69 return idbDatabase.release(); 70 } 71 72 IDBDatabase::IDBDatabase(ScriptExecutionContext* context, PassRefPtr<IDBDatabaseBackendInterface> backend, PassRefPtr<IDBDatabaseCallbacks> callbacks) 73 : ActiveDOMObject(context) 74 , m_backend(backend) 75 , m_closePending(false) 76 , m_contextStopped(false) 77 , m_databaseCallbacks(callbacks) 78 { 79 // We pass a reference of this object before it can be adopted. 80 relaxAdoptionRequirement(); 81 ScriptWrappable::init(this); 82 } 83 84 IDBDatabase::~IDBDatabase() 85 { 86 close(); 87 } 88 89 int64_t IDBDatabase::nextTransactionId() 90 { 91 // Only keep a 32-bit counter to allow ports to use the other 32 92 // bits of the id. 93 AtomicallyInitializedStatic(int, currentTransactionId = 0); 94 return atomicIncrement(¤tTransactionId); 95 } 96 97 void IDBDatabase::indexCreated(int64_t objectStoreId, const IDBIndexMetadata& metadata) 98 { 99 IDBDatabaseMetadata::ObjectStoreMap::iterator it = m_metadata.objectStores.find(objectStoreId); 100 ASSERT(it != m_metadata.objectStores.end()); 101 it->value.indexes.set(metadata.id, metadata); 102 } 103 104 void IDBDatabase::indexDeleted(int64_t objectStoreId, int64_t indexId) 105 { 106 IDBDatabaseMetadata::ObjectStoreMap::iterator it = m_metadata.objectStores.find(objectStoreId); 107 ASSERT(it != m_metadata.objectStores.end()); 108 it->value.indexes.remove(indexId); 109 } 110 111 void IDBDatabase::transactionCreated(IDBTransaction* transaction) 112 { 113 ASSERT(transaction); 114 ASSERT(!m_transactions.contains(transaction->id())); 115 m_transactions.add(transaction->id(), transaction); 116 117 if (transaction->isVersionChange()) { 118 ASSERT(!m_versionChangeTransaction); 119 m_versionChangeTransaction = transaction; 120 } 121 } 122 123 void IDBDatabase::transactionFinished(IDBTransaction* transaction) 124 { 125 ASSERT(transaction); 126 ASSERT(m_transactions.contains(transaction->id())); 127 ASSERT(m_transactions.get(transaction->id()) == transaction); 128 m_transactions.remove(transaction->id()); 129 130 if (transaction->isVersionChange()) { 131 ASSERT(m_versionChangeTransaction == transaction); 132 m_versionChangeTransaction = 0; 133 } 134 135 if (m_closePending && m_transactions.isEmpty()) 136 closeConnection(); 137 } 138 139 void IDBDatabase::onAbort(int64_t transactionId, PassRefPtr<DOMError> error) 140 { 141 ASSERT(m_transactions.contains(transactionId)); 142 m_transactions.get(transactionId)->onAbort(error); 143 } 144 145 void IDBDatabase::onComplete(int64_t transactionId) 146 { 147 ASSERT(m_transactions.contains(transactionId)); 148 m_transactions.get(transactionId)->onComplete(); 149 } 150 151 PassRefPtr<DOMStringList> IDBDatabase::objectStoreNames() const 152 { 153 RefPtr<DOMStringList> objectStoreNames = DOMStringList::create(); 154 for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) 155 objectStoreNames->append(it->value.name); 156 objectStoreNames->sort(); 157 return objectStoreNames.release(); 158 } 159 160 PassRefPtr<IDBAny> IDBDatabase::version() const 161 { 162 int64_t intVersion = m_metadata.intVersion; 163 if (intVersion == IDBDatabaseMetadata::NoIntVersion) 164 return IDBAny::createString(m_metadata.version); 165 return IDBAny::create(intVersion); 166 } 167 168 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const Dictionary& options, ExceptionState& es) 169 { 170 IDBKeyPath keyPath; 171 bool autoIncrement = false; 172 if (!options.isUndefinedOrNull()) { 173 String keyPathString; 174 Vector<String> keyPathArray; 175 if (options.get("keyPath", keyPathArray)) 176 keyPath = IDBKeyPath(keyPathArray); 177 else if (options.getWithUndefinedOrNullCheck("keyPath", keyPathString)) 178 keyPath = IDBKeyPath(keyPathString); 179 180 options.get("autoIncrement", autoIncrement); 181 } 182 183 return createObjectStore(name, keyPath, autoIncrement, es); 184 } 185 186 PassRefPtr<IDBObjectStore> IDBDatabase::createObjectStore(const String& name, const IDBKeyPath& keyPath, bool autoIncrement, ExceptionState& es) 187 { 188 IDB_TRACE("IDBDatabase::createObjectStore"); 189 HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBCreateObjectStoreCall, IDBMethodsMax); 190 if (!m_versionChangeTransaction) { 191 es.throwDOMException(InvalidStateError, IDBDatabase::notVersionChangeTransactionErrorMessage); 192 return 0; 193 } 194 if (m_versionChangeTransaction->isFinished()) { 195 es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage); 196 return 0; 197 } 198 if (!m_versionChangeTransaction->isActive()) { 199 es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage); 200 return 0; 201 } 202 203 if (containsObjectStore(name)) { 204 es.throwDOMException(ConstraintError, "An object store with the specified name already exists."); 205 return 0; 206 } 207 208 if (!keyPath.isNull() && !keyPath.isValid()) { 209 es.throwDOMException(SyntaxError, "The keyPath option is not a valid key path."); 210 return 0; 211 } 212 213 if (autoIncrement && ((keyPath.type() == IDBKeyPath::StringType && keyPath.string().isEmpty()) || keyPath.type() == IDBKeyPath::ArrayType)) { 214 es.throwDOMException(InvalidAccessError, "The autoIncrement option was set but the keyPath option was empty or an array."); 215 return 0; 216 } 217 218 int64_t objectStoreId = m_metadata.maxObjectStoreId + 1; 219 m_backend->createObjectStore(m_versionChangeTransaction->id(), objectStoreId, name, keyPath, autoIncrement); 220 221 IDBObjectStoreMetadata metadata(name, objectStoreId, keyPath, autoIncrement, IDBDatabaseBackendInterface::MinimumIndexId); 222 RefPtr<IDBObjectStore> objectStore = IDBObjectStore::create(metadata, m_versionChangeTransaction.get()); 223 m_metadata.objectStores.set(metadata.id, metadata); 224 ++m_metadata.maxObjectStoreId; 225 226 m_versionChangeTransaction->objectStoreCreated(name, objectStore); 227 return objectStore.release(); 228 } 229 230 void IDBDatabase::deleteObjectStore(const String& name, ExceptionState& es) 231 { 232 IDB_TRACE("IDBDatabase::deleteObjectStore"); 233 HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBDeleteObjectStoreCall, IDBMethodsMax); 234 if (!m_versionChangeTransaction) { 235 es.throwDOMException(InvalidStateError, IDBDatabase::notVersionChangeTransactionErrorMessage); 236 return; 237 } 238 if (m_versionChangeTransaction->isFinished()) { 239 es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionFinishedErrorMessage); 240 return; 241 } 242 if (!m_versionChangeTransaction->isActive()) { 243 es.throwDOMException(TransactionInactiveError, IDBDatabase::transactionInactiveErrorMessage); 244 return; 245 } 246 247 int64_t objectStoreId = findObjectStoreId(name); 248 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 249 es.throwDOMException(NotFoundError, "The specified object store was not found."); 250 return; 251 } 252 253 m_backend->deleteObjectStore(m_versionChangeTransaction->id(), objectStoreId); 254 m_versionChangeTransaction->objectStoreDeleted(name); 255 m_metadata.objectStores.remove(objectStoreId); 256 } 257 258 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const Vector<String>& scope, const String& modeString, ExceptionState& es) 259 { 260 IDB_TRACE("IDBDatabase::transaction"); 261 HistogramSupport::histogramEnumeration("WebCore.IndexedDB.FrontEndAPICalls", IDBTransactionCall, IDBMethodsMax); 262 if (!scope.size()) { 263 es.throwDOMException(InvalidAccessError, "The storeNames parameter was empty."); 264 return 0; 265 } 266 267 IndexedDB::TransactionMode mode = IDBTransaction::stringToMode(modeString, es); 268 if (es.hadException()) 269 return 0; 270 271 if (m_versionChangeTransaction) { 272 es.throwDOMException(InvalidStateError, "A version change transaction is running."); 273 return 0; 274 } 275 276 if (m_closePending) { 277 es.throwDOMException(InvalidStateError, "The database connection is closing."); 278 return 0; 279 } 280 281 Vector<int64_t> objectStoreIds; 282 for (size_t i = 0; i < scope.size(); ++i) { 283 int64_t objectStoreId = findObjectStoreId(scope[i]); 284 if (objectStoreId == IDBObjectStoreMetadata::InvalidId) { 285 es.throwDOMException(NotFoundError, "One of the specified object stores was not found."); 286 return 0; 287 } 288 objectStoreIds.append(objectStoreId); 289 } 290 291 int64_t transactionId = nextTransactionId(); 292 m_backend->createTransaction(transactionId, m_databaseCallbacks, objectStoreIds, mode); 293 294 RefPtr<IDBTransaction> transaction = IDBTransaction::create(context, transactionId, scope, mode, this); 295 return transaction.release(); 296 } 297 298 PassRefPtr<IDBTransaction> IDBDatabase::transaction(ScriptExecutionContext* context, const String& storeName, const String& mode, ExceptionState& es) 299 { 300 RefPtr<DOMStringList> storeNames = DOMStringList::create(); 301 storeNames->append(storeName); 302 return transaction(context, storeNames, mode, es); 303 } 304 305 void IDBDatabase::forceClose() 306 { 307 for (TransactionMap::const_iterator::Values it = m_transactions.begin().values(), end = m_transactions.end().values(); it != end; ++it) 308 (*it)->abort(IGNORE_EXCEPTION); 309 this->close(); 310 enqueueEvent(Event::create(eventNames().closeEvent, false, false)); 311 } 312 313 void IDBDatabase::close() 314 { 315 IDB_TRACE("IDBDatabase::close"); 316 if (m_closePending) 317 return; 318 319 m_closePending = true; 320 321 if (m_transactions.isEmpty()) 322 closeConnection(); 323 } 324 325 void IDBDatabase::closeConnection() 326 { 327 ASSERT(m_closePending); 328 ASSERT(m_transactions.isEmpty()); 329 330 m_backend->close(m_databaseCallbacks); 331 m_backend.clear(); 332 333 if (m_contextStopped || !scriptExecutionContext()) 334 return; 335 336 EventQueue* eventQueue = scriptExecutionContext()->eventQueue(); 337 // Remove any pending versionchange events scheduled to fire on this 338 // connection. They would have been scheduled by the backend when another 339 // connection called setVersion, but the frontend connection is being 340 // closed before they could fire. 341 for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { 342 bool removed = eventQueue->cancelEvent(m_enqueuedEvents[i].get()); 343 ASSERT_UNUSED(removed, removed); 344 } 345 } 346 347 void IDBDatabase::onVersionChange(int64_t oldVersion, int64_t newVersion) 348 { 349 IDB_TRACE("IDBDatabase::onVersionChange"); 350 if (m_contextStopped || !scriptExecutionContext()) 351 return; 352 353 if (m_closePending) 354 return; 355 356 RefPtr<IDBAny> newVersionAny = newVersion == IDBDatabaseMetadata::NoIntVersion ? IDBAny::createNull() : IDBAny::create(newVersion); 357 enqueueEvent(IDBVersionChangeEvent::create(IDBAny::create(oldVersion), newVersionAny.release(), eventNames().versionchangeEvent)); 358 } 359 360 void IDBDatabase::enqueueEvent(PassRefPtr<Event> event) 361 { 362 ASSERT(!m_contextStopped); 363 ASSERT(scriptExecutionContext()); 364 EventQueue* eventQueue = scriptExecutionContext()->eventQueue(); 365 event->setTarget(this); 366 eventQueue->enqueueEvent(event.get()); 367 m_enqueuedEvents.append(event); 368 } 369 370 bool IDBDatabase::dispatchEvent(PassRefPtr<Event> event) 371 { 372 IDB_TRACE("IDBDatabase::dispatchEvent"); 373 ASSERT(event->type() == eventNames().versionchangeEvent || event->type() == eventNames().closeEvent); 374 for (size_t i = 0; i < m_enqueuedEvents.size(); ++i) { 375 if (m_enqueuedEvents[i].get() == event.get()) 376 m_enqueuedEvents.remove(i); 377 } 378 return EventTarget::dispatchEvent(event.get()); 379 } 380 381 int64_t IDBDatabase::findObjectStoreId(const String& name) const 382 { 383 for (IDBDatabaseMetadata::ObjectStoreMap::const_iterator it = m_metadata.objectStores.begin(); it != m_metadata.objectStores.end(); ++it) { 384 if (it->value.name == name) { 385 ASSERT(it->key != IDBObjectStoreMetadata::InvalidId); 386 return it->key; 387 } 388 } 389 return IDBObjectStoreMetadata::InvalidId; 390 } 391 392 bool IDBDatabase::hasPendingActivity() const 393 { 394 // The script wrapper must not be collected before the object is closed or 395 // we can't fire a "versionchange" event to let script manually close the connection. 396 return !m_closePending && !m_eventTargetData.eventListenerMap.isEmpty() && !m_contextStopped; 397 } 398 399 void IDBDatabase::stop() 400 { 401 // Stop fires at a deterministic time, so we need to call close in it. 402 close(); 403 404 m_contextStopped = true; 405 } 406 407 const AtomicString& IDBDatabase::interfaceName() const 408 { 409 return eventNames().interfaceForIDBDatabase; 410 } 411 412 ScriptExecutionContext* IDBDatabase::scriptExecutionContext() const 413 { 414 return ActiveDOMObject::scriptExecutionContext(); 415 } 416 417 EventTargetData* IDBDatabase::eventTargetData() 418 { 419 return &m_eventTargetData; 420 } 421 422 EventTargetData* IDBDatabase::ensureEventTargetData() 423 { 424 return &m_eventTargetData; 425 } 426 427 } // namespace WebCore 428