diff options
Diffstat (limited to 'WebCore/storage')
-rw-r--r-- | WebCore/storage/AbstractDatabase.cpp | 413 | ||||
-rw-r--r-- | WebCore/storage/AbstractDatabase.h | 74 | ||||
-rw-r--r-- | WebCore/storage/Database.cpp | 524 | ||||
-rw-r--r-- | WebCore/storage/Database.h | 88 | ||||
-rw-r--r-- | WebCore/storage/DatabaseAuthorizer.cpp | 14 | ||||
-rw-r--r-- | WebCore/storage/DatabaseAuthorizer.h | 13 | ||||
-rw-r--r-- | WebCore/storage/DatabaseSync.cpp | 99 | ||||
-rw-r--r-- | WebCore/storage/DatabaseSync.h | 32 | ||||
-rw-r--r-- | WebCore/storage/DatabaseTask.cpp | 35 | ||||
-rw-r--r-- | WebCore/storage/DatabaseTask.h | 27 | ||||
-rw-r--r-- | WebCore/storage/DatabaseThread.cpp | 4 | ||||
-rw-r--r-- | WebCore/storage/DatabaseTracker.cpp | 12 | ||||
-rw-r--r-- | WebCore/storage/DatabaseTracker.h | 3 | ||||
-rw-r--r-- | WebCore/storage/IDBCallbacks.h | 2 | ||||
-rw-r--r-- | WebCore/storage/IDBKey.cpp | 62 | ||||
-rw-r--r-- | WebCore/storage/IDBKey.h | 90 | ||||
-rw-r--r-- | WebCore/storage/IDBKey.idl | 34 | ||||
-rw-r--r-- | WebCore/storage/IDBKeyTree.h | 157 | ||||
-rw-r--r-- | WebCore/storage/IDBRequest.cpp | 5 | ||||
-rw-r--r-- | WebCore/storage/IDBRequest.h | 1 | ||||
-rw-r--r-- | WebCore/storage/SQLTransaction.cpp | 4 |
21 files changed, 1030 insertions, 663 deletions
diff --git a/WebCore/storage/AbstractDatabase.cpp b/WebCore/storage/AbstractDatabase.cpp index dc3e8e2..ca9a1b5 100644 --- a/WebCore/storage/AbstractDatabase.cpp +++ b/WebCore/storage/AbstractDatabase.cpp @@ -30,9 +30,128 @@ #include "AbstractDatabase.h" #if ENABLE(DATABASE) +#include "DatabaseAuthorizer.h" +#include "DatabaseTracker.h" +#include "ExceptionCode.h" +#include "Logging.h" +#include "SQLiteStatement.h" +#include "ScriptExecutionContext.h" +#include "SecurityOrigin.h" +#include "StringHash.h" +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/StdLibExtras.h> namespace WebCore { +static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) +{ + SQLiteStatement statement(db, query); + int result = statement.prepare(); + + if (result != SQLResultOk) { + LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); + return false; + } + + result = statement.step(); + if (result == SQLResultRow) { + resultString = statement.getColumnText(0); + return true; + } + if (result == SQLResultDone) { + resultString = String(); + return true; + } + + LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); + return false; +} + +static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) +{ + SQLiteStatement statement(db, query); + int result = statement.prepare(); + + if (result != SQLResultOk) { + LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); + return false; + } + + statement.bindText(1, value); + + result = statement.step(); + if (result != SQLResultDone) { + LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); + return false; + } + + return true; +} + +// FIXME: move all guid-related functions to a DatabaseVersionTracker class. +static Mutex& guidMutex() +{ + // Note: We don't have to use AtomicallyInitializedStatic here because + // this function is called once in the constructor on the main thread + // before any other threads that call this function are used. + DEFINE_STATIC_LOCAL(Mutex, mutex, ()); + return mutex; +} + +typedef HashMap<int, String> GuidVersionMap; +static GuidVersionMap& guidToVersionMap() +{ + DEFINE_STATIC_LOCAL(GuidVersionMap, map, ()); + return map; +} + +// NOTE: Caller must lock guidMutex(). +static inline void updateGuidVersionMap(int guid, String newVersion) +{ + // Ensure the the mutex is locked. + ASSERT(!guidMutex().tryLock()); + + // Note: It is not safe to put an empty string into the guidToVersionMap() map. + // That's because the map is cross-thread, but empty strings are per-thread. + // The copy() function makes a version of the string you can use on the current + // thread, but we need a string we can keep in a cross-thread data structure. + // FIXME: This is a quite-awkward restriction to have to program with. + + // Map null string to empty string (see comment above). + guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy()); +} + +typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap; +static GuidDatabaseMap& guidToDatabaseMap() +{ + DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ()); + return map; +} + +static int guidForOriginAndName(const String& origin, const String& name) +{ + String stringID = origin + "/" + name; + + // Note: We don't have to use AtomicallyInitializedStatic here because + // this function is called once in the constructor on the main thread + // before any other threads that call this function are used. + DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ()); + MutexLocker locker(stringIdentifierMutex); + typedef HashMap<String, int> IDGuidMap; + DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ()); + int guid = stringIdentifierToGUIDMap.get(stringID); + if (!guid) { + static int currentNewGUID = 1; + guid = currentNewGUID++; + stringIdentifierToGUIDMap.set(stringID, guid); + } + + return guid; +} + static bool isDatabaseAvailable = true; bool AbstractDatabase::isAvailable() @@ -45,10 +164,304 @@ void AbstractDatabase::setIsAvailable(bool available) isDatabaseAvailable = available; } +// static +const String& AbstractDatabase::databaseInfoTableName() +{ + DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__")); + return name; +} + +AbstractDatabase::AbstractDatabase(ScriptExecutionContext* context, const String& name, const String& expectedVersion, + const String& displayName, unsigned long estimatedSize) + : m_scriptExecutionContext(context) + , m_name(name.crossThreadString()) + , m_expectedVersion(expectedVersion.crossThreadString()) + , m_displayName(displayName.crossThreadString()) + , m_estimatedSize(estimatedSize) + , m_guid(0) + , m_opened(false) + , m_new(false) +{ + ASSERT(context->isContextThread()); + m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin(); + + m_databaseAuthorizer = DatabaseAuthorizer::create(databaseInfoTableName()); + + if (m_name.isNull()) + m_name = ""; + + m_guid = guidForOriginAndName(securityOrigin()->toString(), name); + { + MutexLocker locker(guidMutex()); + + HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid); + if (!hashSet) { + hashSet = new HashSet<AbstractDatabase*>; + guidToDatabaseMap().set(m_guid, hashSet); + } + + hashSet->add(this); + } + + m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name); + DatabaseTracker::tracker().addOpenDatabase(this); +} + AbstractDatabase::~AbstractDatabase() { } +void AbstractDatabase::closeDatabase() +{ + if (!m_opened) + return; + + m_sqliteDatabase.close(); + m_opened = false; + { + MutexLocker locker(guidMutex()); + + HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid); + ASSERT(hashSet); + ASSERT(hashSet->contains(this)); + hashSet->remove(this); + if (hashSet->isEmpty()) { + guidToDatabaseMap().remove(m_guid); + delete hashSet; + guidToVersionMap().remove(m_guid); + } + } +} + +String AbstractDatabase::version() const +{ + MutexLocker locker(guidMutex()); + return guidToVersionMap().get(m_guid).threadsafeCopy(); +} + +static const int maxSqliteBusyWaitTime = 30000; +bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec) +{ + if (!m_sqliteDatabase.open(m_filename, true)) { + LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); + ec = INVALID_STATE_ERR; + return false; + } + if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum()) + LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data()); + + ASSERT(m_databaseAuthorizer); + m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); + m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); + + String currentVersion; + { + MutexLocker locker(guidMutex()); + + GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); + if (entry != guidToVersionMap().end()) { + // Map null string to empty string (see updateGuidVersionMap()). + currentVersion = entry->second.isNull() ? String("") : entry->second; + LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); + } else { + LOG(StorageAPI, "No cached version for guid %i", m_guid); + + if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { + m_new = true; + + if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { + LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); + ec = INVALID_STATE_ERR; + // Close the handle to the database file. + m_sqliteDatabase.close(); + return false; + } + } + + if (!getVersionFromDatabase(currentVersion)) { + LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); + ec = INVALID_STATE_ERR; + // Close the handle to the database file. + m_sqliteDatabase.close(); + return false; + } + if (currentVersion.length()) { + LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); + } else if (!m_new || shouldSetVersionInNewDatabase) { + LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); + if (!setVersionInDatabase(m_expectedVersion)) { + LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); + ec = INVALID_STATE_ERR; + // Close the handle to the database file. + m_sqliteDatabase.close(); + return false; + } + currentVersion = m_expectedVersion; + } + + updateGuidVersionMap(m_guid, currentVersion); + } + } + + if (currentVersion.isNull()) { + LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); + currentVersion = ""; + } + + // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception. + // If the expected version is the empty string, then we always return with whatever version of the database we have. + if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && m_expectedVersion != currentVersion) { + LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(), + databaseDebugName().ascii().data(), currentVersion.ascii().data()); + ec = INVALID_STATE_ERR; + // Close the handle to the database file. + m_sqliteDatabase.close(); + return false; + } + + m_opened = true; + + return true; +} + +ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const +{ + return m_scriptExecutionContext.get(); +} + +SecurityOrigin* AbstractDatabase::securityOrigin() const +{ + return m_contextThreadSecurityOrigin.get(); +} + +String AbstractDatabase::stringIdentifier() const +{ + // Return a deep copy for ref counting thread safety + return m_name.threadsafeCopy(); +} + +String AbstractDatabase::displayName() const +{ + // Return a deep copy for ref counting thread safety + return m_displayName.threadsafeCopy(); +} + +unsigned long AbstractDatabase::estimatedSize() const +{ + return m_estimatedSize; +} + +String AbstractDatabase::fileName() const +{ + // Return a deep copy for ref counting thread safety + return m_filename.threadsafeCopy(); +} + +// static +const String& AbstractDatabase::databaseVersionKey() +{ + DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey")); + return key; +} + +bool AbstractDatabase::getVersionFromDatabase(String& version) +{ + DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';")); + + m_databaseAuthorizer->disable(); + + bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version); + if (!result) + LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); + + m_databaseAuthorizer->enable(); + + return result; +} + +bool AbstractDatabase::setVersionInDatabase(const String& version) +{ + // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE + // clause in the CREATE statement (see Database::performOpenAndVerify()). + DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);")); + + m_databaseAuthorizer->disable(); + + bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version); + if (!result) + LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); + + m_databaseAuthorizer->enable(); + + return result; +} + +bool AbstractDatabase::versionMatchesExpected() const +{ + if (!m_expectedVersion.isEmpty()) { + MutexLocker locker(guidMutex()); + return m_expectedVersion == guidToVersionMap().get(m_guid); + } + + return true; +} + +void AbstractDatabase::setExpectedVersion(const String& version) +{ + m_expectedVersion = version.threadsafeCopy(); + // Update the in memory database version map. + MutexLocker locker(guidMutex()); + updateGuidVersionMap(m_guid, version); +} + +void AbstractDatabase::disableAuthorizer() +{ + ASSERT(m_databaseAuthorizer); + m_databaseAuthorizer->disable(); +} + +void AbstractDatabase::enableAuthorizer() +{ + ASSERT(m_databaseAuthorizer); + m_databaseAuthorizer->enable(); +} + +void AbstractDatabase::setAuthorizerReadOnly() +{ + ASSERT(m_databaseAuthorizer); + m_databaseAuthorizer->setReadOnly(); +} + +bool AbstractDatabase::lastActionChangedDatabase() +{ + ASSERT(m_databaseAuthorizer); + return m_databaseAuthorizer->lastActionChangedDatabase(); +} + +bool AbstractDatabase::lastActionWasInsert() +{ + ASSERT(m_databaseAuthorizer); + return m_databaseAuthorizer->lastActionWasInsert(); +} + +void AbstractDatabase::resetDeletes() +{ + ASSERT(m_databaseAuthorizer); + m_databaseAuthorizer->resetDeletes(); +} + +bool AbstractDatabase::hadDeletes() +{ + ASSERT(m_databaseAuthorizer); + return m_databaseAuthorizer->hadDeletes(); +} + +void AbstractDatabase::resetAuthorizer() +{ + if (m_databaseAuthorizer) + m_databaseAuthorizer->reset(); +} + } // namespace WebCore #endif // ENABLE(DATABASE) diff --git a/WebCore/storage/AbstractDatabase.h b/WebCore/storage/AbstractDatabase.h index 5e4e0fa..d38a819 100644 --- a/WebCore/storage/AbstractDatabase.h +++ b/WebCore/storage/AbstractDatabase.h @@ -32,13 +32,21 @@ #if ENABLE(DATABASE) #include "PlatformString.h" +#include "SQLiteDatabase.h" +#include <wtf/Forward.h> #include <wtf/ThreadSafeShared.h> +#ifndef NDEBUG +#include "SecurityOrigin.h" +#endif namespace WebCore { +class DatabaseAuthorizer; class ScriptExecutionContext; class SecurityOrigin; +typedef int ExceptionCode; + class AbstractDatabase : public ThreadSafeShared<AbstractDatabase> { public: static bool isAvailable(); @@ -46,15 +54,69 @@ public: virtual ~AbstractDatabase(); - virtual ScriptExecutionContext* scriptExecutionContext() const = 0; - virtual SecurityOrigin* securityOrigin() const = 0; - virtual String stringIdentifier() const = 0; - virtual String displayName() const = 0; - virtual unsigned long estimatedSize() const = 0; - virtual String fileName() const = 0; + virtual String version() const; + + bool opened() const { return m_opened; } + bool isNew() const { return m_new; } + + virtual ScriptExecutionContext* scriptExecutionContext() const; + virtual SecurityOrigin* securityOrigin() const; + virtual String stringIdentifier() const; + virtual String displayName() const; + virtual unsigned long estimatedSize() const; + virtual String fileName() const; + + // FIXME: move all version-related methods to a DatabaseVersionTracker class + bool versionMatchesExpected() const; + void setExpectedVersion(const String& version); + bool getVersionFromDatabase(String& version); + bool setVersionInDatabase(const String& version); + + void disableAuthorizer(); + void enableAuthorizer(); + void setAuthorizerReadOnly(); + bool lastActionChangedDatabase(); + bool lastActionWasInsert(); + void resetDeletes(); + bool hadDeletes(); + void resetAuthorizer(); virtual void markAsDeletedAndClose() = 0; virtual void closeImmediately() = 0; + +protected: + AbstractDatabase(ScriptExecutionContext*, const String& name, const String& expectedVersion, + const String& displayName, unsigned long estimatedSize); + + void closeDatabase(); + + virtual bool performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec); + + static const String& databaseInfoTableName(); + + RefPtr<ScriptExecutionContext> m_scriptExecutionContext; + RefPtr<SecurityOrigin> m_contextThreadSecurityOrigin; + + String m_name; + String m_expectedVersion; + String m_displayName; + unsigned long m_estimatedSize; + String m_filename; + + SQLiteDatabase m_sqliteDatabase; + +#ifndef NDEBUG + String databaseDebugName() const { return m_contextThreadSecurityOrigin->toString() + "::" + m_name; } +#endif + +private: + static const String& databaseVersionKey(); + + int m_guid; + bool m_opened; + bool m_new; + + RefPtr<DatabaseAuthorizer> m_databaseAuthorizer; }; } // namespace WebCore diff --git a/WebCore/storage/Database.cpp b/WebCore/storage/Database.cpp index ad6b92c..30087ee 100644 --- a/WebCore/storage/Database.cpp +++ b/WebCore/storage/Database.cpp @@ -31,7 +31,6 @@ #if ENABLE(DATABASE) #include "ChangeVersionWrapper.h" -#include "DatabaseAuthorizer.h" #include "DatabaseCallback.h" #include "DatabaseTask.h" #include "DatabaseThread.h" @@ -50,7 +49,6 @@ #include "ScriptController.h" #include "ScriptExecutionContext.h" #include "SecurityOrigin.h" -#include "StringHash.h" #include "VoidCallback.h" #include <wtf/OwnPtr.h> #include <wtf/PassOwnPtr.h> @@ -64,81 +62,27 @@ namespace WebCore { -// If we sleep for more the 30 seconds while blocked on SQLITE_BUSY, give up. -static const int maxSqliteBusyWaitTime = 30000; - -const String& Database::databaseInfoTableName() -{ - DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__")); - return name; -} - -static Mutex& guidMutex() -{ - // Note: We don't have to use AtomicallyInitializedStatic here because - // this function is called once in the constructor on the main thread - // before any other threads that call this function are used. - DEFINE_STATIC_LOCAL(Mutex, mutex, ()); - return mutex; -} - -typedef HashMap<int, String> GuidVersionMap; -static GuidVersionMap& guidToVersionMap() -{ - DEFINE_STATIC_LOCAL(GuidVersionMap, map, ()); - return map; -} - -// NOTE: Caller must lock guidMutex(). -static inline void updateGuidVersionMap(int guid, String newVersion) -{ - // Ensure the the mutex is locked. - ASSERT(!guidMutex().tryLock()); - - // Note: It is not safe to put an empty string into the guidToVersionMap() map. - // That's because the map is cross-thread, but empty strings are per-thread. - // The copy() function makes a version of the string you can use on the current - // thread, but we need a string we can keep in a cross-thread data structure. - // FIXME: This is a quite-awkward restriction to have to program with. - - // Map null string to empty string (see comment above). - guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy()); -} - -typedef HashMap<int, HashSet<Database*>*> GuidDatabaseMap; -static GuidDatabaseMap& guidToDatabaseMap() -{ - DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ()); - return map; -} - -static const String& databaseVersionKey() -{ - DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey")); - return key; -} - -static int guidForOriginAndName(const String& origin, const String& name); - class DatabaseCreationCallbackTask : public ScriptExecutionContext::Task { public: - static PassOwnPtr<DatabaseCreationCallbackTask> create(PassRefPtr<Database> database) + static PassOwnPtr<DatabaseCreationCallbackTask> create(PassRefPtr<Database> database, PassRefPtr<DatabaseCallback> creationCallback) { - return new DatabaseCreationCallbackTask(database); + return new DatabaseCreationCallbackTask(database, creationCallback); } - virtual void performTask(ScriptExecutionContext*) + virtual void performTask(ScriptExecutionContext* context) { - m_database->performCreationCallback(); + m_creationCallback->handleEvent(context, m_database.get()); } private: - DatabaseCreationCallbackTask(PassRefPtr<Database> database) + DatabaseCreationCallbackTask(PassRefPtr<Database> database, PassRefPtr<DatabaseCallback> callback) : m_database(database) + , m_creationCallback(callback) { } RefPtr<Database> m_database; + RefPtr<DatabaseCallback> m_creationCallback; }; PassRefPtr<Database> Database::openDatabase(ScriptExecutionContext* context, const String& name, @@ -147,16 +91,14 @@ PassRefPtr<Database> Database::openDatabase(ScriptExecutionContext* context, con ExceptionCode& e) { if (!DatabaseTracker::tracker().canEstablishDatabase(context, name, displayName, estimatedSize)) { - // FIXME: There should be an exception raised here in addition to returning a null Database object. The question has been raised with the WHATWG. LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), context->securityOrigin()->toString().ascii().data()); return 0; } - RefPtr<Database> database = adoptRef(new Database(context, name, expectedVersion, displayName, estimatedSize, creationCallback)); + RefPtr<Database> database = adoptRef(new Database(context, name, expectedVersion, displayName, estimatedSize)); - if (!database->openAndVerifyVersion(e)) { + if (!database->openAndVerifyVersion(!creationCallback, e)) { LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data()); - context->removeOpenDatabase(database.get()); DatabaseTracker::tracker().removeOpenDatabase(database.get()); return 0; } @@ -176,57 +118,25 @@ PassRefPtr<Database> Database::openDatabase(ScriptExecutionContext* context, con // version to "" and schedule the creation callback. Because of some subtle String // implementation issues, we have to reset m_expectedVersion here instead of doing // it inside performOpenAndVerify() which is run on the DB thread. - if (database->isNew() && database->m_creationCallback.get()) { + if (database->isNew() && creationCallback.get()) { database->m_expectedVersion = ""; LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get()); - database->m_scriptExecutionContext->postTask(DatabaseCreationCallbackTask::create(database)); + database->m_scriptExecutionContext->postTask(DatabaseCreationCallbackTask::create(database, creationCallback)); } return database; } -Database::Database(ScriptExecutionContext* context, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback) - : m_transactionInProgress(false) +Database::Database(ScriptExecutionContext* context, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize) + : AbstractDatabase(context, name, expectedVersion, displayName, estimatedSize) + , m_transactionInProgress(false) , m_isTransactionQueueEnabled(true) - , m_scriptExecutionContext(context) - , m_name(name.crossThreadString()) - , m_guid(0) - , m_expectedVersion(expectedVersion.crossThreadString()) - , m_displayName(displayName.crossThreadString()) - , m_estimatedSize(estimatedSize) , m_deleted(false) - , m_stopped(false) - , m_opened(false) - , m_new(false) - , m_creationCallback(creationCallback) { - ASSERT(m_scriptExecutionContext.get()); - m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin(); m_databaseThreadSecurityOrigin = m_contextThreadSecurityOrigin->threadsafeCopy(); - if (m_name.isNull()) - m_name = ""; ScriptController::initializeThreading(); - - m_guid = guidForOriginAndName(securityOrigin()->toString(), name); - - { - MutexLocker locker(guidMutex()); - - HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid); - if (!hashSet) { - hashSet = new HashSet<Database*>; - guidToDatabaseMap().set(m_guid, hashSet); - } - - hashSet->add(this); - } - ASSERT(m_scriptExecutionContext->databaseThread()); - m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name); - - DatabaseTracker::tracker().addOpenDatabase(this); - context->addOpenDatabase(this); } class DerefContextTask : public ScriptExecutionContext::Task { @@ -253,15 +163,21 @@ Database::~Database() } } -bool Database::openAndVerifyVersion(ExceptionCode& e) +String Database::version() const +{ + if (m_deleted) + return String(); + return AbstractDatabase::version(); +} + +bool Database::openAndVerifyVersion(bool setVersionInNewDatabase, ExceptionCode& e) { if (!m_scriptExecutionContext->databaseThread()) return false; - m_databaseAuthorizer = DatabaseAuthorizer::create(); bool success = false; DatabaseTaskSynchronizer synchronizer; - OwnPtr<DatabaseOpenTask> task = DatabaseOpenTask::create(this, &synchronizer, e, success); + OwnPtr<DatabaseOpenTask> task = DatabaseOpenTask::create(this, setVersionInNewDatabase, &synchronizer, e, success); m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); synchronizer.waitForTaskCompletion(); @@ -269,93 +185,6 @@ bool Database::openAndVerifyVersion(ExceptionCode& e) return success; } - -static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) -{ - SQLiteStatement statement(db, query); - int result = statement.prepare(); - - if (result != SQLResultOk) { - LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); - return false; - } - - result = statement.step(); - if (result == SQLResultRow) { - resultString = statement.getColumnText(0); - return true; - } else if (result == SQLResultDone) { - resultString = String(); - return true; - } else { - LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); - return false; - } -} - -bool Database::getVersionFromDatabase(String& version) -{ - DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';")); - - m_databaseAuthorizer->disable(); - - bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version); - if (!result) - LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); - - m_databaseAuthorizer->enable(); - - return result; -} - -static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) -{ - SQLiteStatement statement(db, query); - int result = statement.prepare(); - - if (result != SQLResultOk) { - LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); - return false; - } - - statement.bindText(1, value); - - result = statement.step(); - if (result != SQLResultDone) { - LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); - return false; - } - - return true; -} - -bool Database::setVersionInDatabase(const String& version) -{ - // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE - // clause in the CREATE statement (see Database::performOpenAndVerify()). - DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);")); - - m_databaseAuthorizer->disable(); - - bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version); - if (!result) - LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); - - m_databaseAuthorizer->enable(); - - return result; -} - -bool Database::versionMatchesExpected() const -{ - if (!m_expectedVersion.isEmpty()) { - MutexLocker locker(guidMutex()); - return m_expectedVersion == guidToVersionMap().get(m_guid); - } - - return true; -} - void Database::markAsDeletedAndClose() { if (m_deleted || !m_scriptExecutionContext->databaseThread()) @@ -369,100 +198,38 @@ void Database::markAsDeletedAndClose() return; } - m_scriptExecutionContext->databaseThread()->unscheduleDatabaseTasks(this); - DatabaseTaskSynchronizer synchronizer; - OwnPtr<DatabaseCloseTask> task = DatabaseCloseTask::create(this, DoNotRemoveDatabaseFromContext, &synchronizer); + OwnPtr<DatabaseCloseTask> task = DatabaseCloseTask::create(this, &synchronizer); m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); synchronizer.waitForTaskCompletion(); - - // DatabaseCloseTask tells Database::close not to do these two removals so that we can do them here synchronously. - m_scriptExecutionContext->removeOpenDatabase(this); - DatabaseTracker::tracker().removeOpenDatabase(this); } -class ContextRemoveOpenDatabaseTask : public ScriptExecutionContext::Task { -public: - static PassOwnPtr<ContextRemoveOpenDatabaseTask> create(PassRefPtr<Database> database) - { - return new ContextRemoveOpenDatabaseTask(database); - } - - virtual void performTask(ScriptExecutionContext* context) - { - context->removeOpenDatabase(m_database.get()); - DatabaseTracker::tracker().removeOpenDatabase(m_database.get()); - } - - virtual bool isCleanupTask() const { return true; } - -private: - ContextRemoveOpenDatabaseTask(PassRefPtr<Database> database) - : m_database(database) - { - } - - RefPtr<Database> m_database; -}; - -void Database::close(ClosePolicy policy) +void Database::close() { - RefPtr<Database> protect = this; - - if (!m_opened) - return; - ASSERT(m_scriptExecutionContext->databaseThread()); ASSERT(currentThread() == m_scriptExecutionContext->databaseThread()->getThreadID()); - m_sqliteDatabase.close(); - // Must ref() before calling databaseThread()->recordDatabaseClosed(). - m_scriptExecutionContext->databaseThread()->recordDatabaseClosed(this); - m_opened = false; { - MutexLocker locker(guidMutex()); - - HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid); - ASSERT(hashSet); - ASSERT(hashSet->contains(this)); - hashSet->remove(this); - if (hashSet->isEmpty()) { - guidToDatabaseMap().remove(m_guid); - delete hashSet; - guidToVersionMap().remove(m_guid); - } + MutexLocker locker(m_transactionInProgressMutex); + m_isTransactionQueueEnabled = false; + m_transactionInProgress = false; } + closeDatabase(); + + // Must ref() before calling databaseThread()->recordDatabaseClosed(). + RefPtr<Database> protect = this; + m_scriptExecutionContext->databaseThread()->recordDatabaseClosed(this); m_scriptExecutionContext->databaseThread()->unscheduleDatabaseTasks(this); - if (policy == RemoveDatabaseFromContext) - m_scriptExecutionContext->postTask(ContextRemoveOpenDatabaseTask::create(this)); + DatabaseTracker::tracker().removeOpenDatabase(this); } void Database::closeImmediately() { DatabaseThread* databaseThread = scriptExecutionContext()->databaseThread(); - if (databaseThread && !databaseThread->terminationRequested()) { - stop(); - databaseThread->scheduleTask(DatabaseCloseTask::create(this, Database::RemoveDatabaseFromContext, 0)); - } -} - -void Database::stop() -{ - // FIXME: The net effect of the following code is to remove all pending transactions and statements, but allow the current statement - // to run to completion. In the future we can use the sqlite3_progress_handler or sqlite3_interrupt interfaces to cancel the current - // statement in response to close(), as well. - - // This method is meant to be used as an analog to cancelling a loader, and is used when a document is shut down as the result of - // a page load or closing the page - m_stopped = true; - - { - MutexLocker locker(m_transactionInProgressMutex); - m_isTransactionQueueEnabled = false; - m_transactionInProgress = false; - } + if (databaseThread && !databaseThread->terminationRequested() && opened()) + databaseThread->scheduleImmediateTask(DatabaseCloseTask::create(this, 0)); } unsigned long long Database::maximumSize() const @@ -470,179 +237,16 @@ unsigned long long Database::maximumSize() const return DatabaseTracker::tracker().getMaxSizeForDatabase(this); } -void Database::disableAuthorizer() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->disable(); -} - -void Database::enableAuthorizer() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->enable(); -} - -void Database::setAuthorizerReadOnly() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->setReadOnly(); -} - -bool Database::lastActionChangedDatabase() -{ - ASSERT(m_databaseAuthorizer); - return m_databaseAuthorizer->lastActionChangedDatabase(); -} - -bool Database::lastActionWasInsert() -{ - ASSERT(m_databaseAuthorizer); - return m_databaseAuthorizer->lastActionWasInsert(); -} - -void Database::resetDeletes() -{ - ASSERT(m_databaseAuthorizer); - m_databaseAuthorizer->resetDeletes(); -} - -bool Database::hadDeletes() -{ - ASSERT(m_databaseAuthorizer); - return m_databaseAuthorizer->hadDeletes(); -} - -static int guidForOriginAndName(const String& origin, const String& name) -{ - String stringID; - if (origin.endsWith("/")) - stringID = origin + name; - else - stringID = origin + "/" + name; - - // Note: We don't have to use AtomicallyInitializedStatic here because - // this function is called once in the constructor on the main thread - // before any other threads that call this function are used. - DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ()); - MutexLocker locker(stringIdentifierMutex); - typedef HashMap<String, int> IDGuidMap; - DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ()); - int guid = stringIdentifierToGUIDMap.get(stringID); - if (!guid) { - static int currentNewGUID = 1; - guid = currentNewGUID++; - stringIdentifierToGUIDMap.set(stringID, guid); - } - - return guid; -} - -void Database::resetAuthorizer() +bool Database::performOpenAndVerify(bool setVersionInNewDatabase, ExceptionCode& e) { - if (m_databaseAuthorizer) - m_databaseAuthorizer->reset(); -} - -void Database::performPolicyChecks() -{ - // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates. - // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to - // increase the size limit - - /* - if (m_databaseAuthorizer->lastActionIncreasedSize()) - DatabaseTracker::scheduleFileSizeCheckOnMainThread(this); - */ - - notImplemented(); -} + if (AbstractDatabase::performOpenAndVerify(setVersionInNewDatabase, e)) { + if (m_scriptExecutionContext->databaseThread()) + m_scriptExecutionContext->databaseThread()->recordDatabaseOpen(this); -bool Database::performOpenAndVerify(ExceptionCode& e) -{ - if (!m_sqliteDatabase.open(m_filename, true)) { - LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); - e = INVALID_STATE_ERR; - return false; - } - if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum()) - LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data()); - - ASSERT(m_databaseAuthorizer); - m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); - m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); - - String currentVersion; - { - MutexLocker locker(guidMutex()); - - GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); - if (entry != guidToVersionMap().end()) { - // Map null string to empty string (see updateGuidVersionMap()). - currentVersion = entry->second.isNull() ? String("") : entry->second; - LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); - } else { - LOG(StorageAPI, "No cached version for guid %i", m_guid); - - if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { - m_new = true; - - if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { - LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); - e = INVALID_STATE_ERR; - // Close the handle to the database file. - m_sqliteDatabase.close(); - return false; - } - } - - if (!getVersionFromDatabase(currentVersion)) { - LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); - e = INVALID_STATE_ERR; - // Close the handle to the database file. - m_sqliteDatabase.close(); - return false; - } - if (currentVersion.length()) { - LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); - } else if (!m_new || !m_creationCallback) { - LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); - if (!setVersionInDatabase(m_expectedVersion)) { - LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); - e = INVALID_STATE_ERR; - // Close the handle to the database file. - m_sqliteDatabase.close(); - return false; - } - currentVersion = m_expectedVersion; - } - - updateGuidVersionMap(m_guid, currentVersion); - } - } - - if (currentVersion.isNull()) { - LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); - currentVersion = ""; - } - - // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception. - // If the expected version is the empty string, then we always return with whatever version of the database we have. - if ((!m_new || !m_creationCallback) && m_expectedVersion.length() && m_expectedVersion != currentVersion) { - LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(), - databaseDebugName().ascii().data(), currentVersion.ascii().data()); - e = INVALID_STATE_ERR; - // Close the handle to the database file. - m_sqliteDatabase.close(); - return false; + return true; } - // All checks passed and we still have a handle to this database file. - // Make sure DatabaseThread closes it when DatabaseThread goes away. - m_opened = true; - if (m_scriptExecutionContext->databaseThread()) - m_scriptExecutionContext->databaseThread()->recordDatabaseOpen(this); - - return true; + return false; } void Database::changeVersion(const String& oldVersion, const String& newVersion, @@ -757,11 +361,6 @@ Vector<String> Database::performGetTableNames() return tableNames; } -void Database::performCreationCallback() -{ - m_creationCallback->handleEvent(m_scriptExecutionContext.get(), this); -} - SQLTransactionClient* Database::transactionClient() const { return m_scriptExecutionContext->databaseThread()->transactionClient(); @@ -772,14 +371,6 @@ SQLTransactionCoordinator* Database::transactionCoordinator() const return m_scriptExecutionContext->databaseThread()->transactionCoordinator(); } -String Database::version() const -{ - if (m_deleted) - return String(); - MutexLocker locker(guidMutex()); - return guidToVersionMap().get(m_guid).threadsafeCopy(); -} - Vector<String> Database::tableNames() { // FIXME: Not using threadsafeCopy on these strings looks ok since threads take strict turns @@ -797,14 +388,6 @@ Vector<String> Database::tableNames() return result; } -void Database::setExpectedVersion(const String& version) -{ - m_expectedVersion = version.threadsafeCopy(); - // Update the in memory database version map. - MutexLocker locker(guidMutex()); - updateGuidVersionMap(m_guid, version); -} - SecurityOrigin* Database::securityOrigin() const { if (m_scriptExecutionContext->isContextThread()) @@ -814,29 +397,6 @@ SecurityOrigin* Database::securityOrigin() const return 0; } -String Database::stringIdentifier() const -{ - // Return a deep copy for ref counting thread safety - return m_name.threadsafeCopy(); -} - -String Database::displayName() const -{ - // Return a deep copy for ref counting thread safety - return m_displayName.threadsafeCopy(); -} - -unsigned long Database::estimatedSize() const -{ - return m_estimatedSize; -} - -String Database::fileName() const -{ - // Return a deep copy for ref counting thread safety - return m_filename.threadsafeCopy(); -} - void Database::incrementalVacuumIfNeeded() { int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize(); diff --git a/WebCore/storage/Database.h b/WebCore/storage/Database.h index 495f98f..22ef55e 100644 --- a/WebCore/storage/Database.h +++ b/WebCore/storage/Database.h @@ -33,18 +33,13 @@ #include "AbstractDatabase.h" #include "PlatformString.h" #include "SQLiteDatabase.h" -#ifndef NDEBUG -#include "SecurityOrigin.h" -#endif #include <wtf/Deque.h> #include <wtf/Forward.h> namespace WebCore { -class DatabaseAuthorizer; class DatabaseCallback; -class DatabaseThread; class ScriptExecutionContext; class SecurityOrigin; class SQLTransaction; @@ -54,8 +49,6 @@ class SQLTransactionCoordinator; class SQLTransactionErrorCallback; class VoidCallback; -typedef int ExceptionCode; - class Database : public AbstractDatabase { public: virtual ~Database(); @@ -63,113 +56,62 @@ public: // Direct support for the DOM API static PassRefPtr<Database> openDatabase(ScriptExecutionContext*, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback>, ExceptionCode&); - String version() const; + virtual String version() const; void changeVersion(const String& oldVersion, const String& newVersion, PassRefPtr<SQLTransactionCallback>, PassRefPtr<SQLTransactionErrorCallback>, PassRefPtr<VoidCallback> successCallback); void transaction(PassRefPtr<SQLTransactionCallback>, PassRefPtr<SQLTransactionErrorCallback>, PassRefPtr<VoidCallback> successCallback, bool readOnly); // Internal engine support - static const String& databaseInfoTableName(); - - void disableAuthorizer(); - void enableAuthorizer(); - void setAuthorizerReadOnly(); - bool lastActionChangedDatabase(); - bool lastActionWasInsert(); - void resetDeletes(); - bool hadDeletes(); - Vector<String> tableNames(); - virtual ScriptExecutionContext* scriptExecutionContext() const { return m_scriptExecutionContext.get(); } virtual SecurityOrigin* securityOrigin() const; SQLiteDatabase& sqliteDatabase() { return m_sqliteDatabase; } - virtual String stringIdentifier() const; - virtual String displayName() const; - virtual unsigned long estimatedSize() const; - virtual String fileName() const; - - bool getVersionFromDatabase(String&); - bool setVersionInDatabase(const String&); - void setExpectedVersion(const String&); - bool versionMatchesExpected() const; virtual void markAsDeletedAndClose(); bool deleted() const { return m_deleted; } - enum ClosePolicy { DoNotRemoveDatabaseFromContext, RemoveDatabaseFromContext }; - void close(ClosePolicy); + void close(); virtual void closeImmediately(); - bool opened() const { return m_opened; } - - void stop(); - bool stopped() const { return m_stopped; } - - bool isNew() const { return m_new; } unsigned long long databaseSize() const; unsigned long long maximumSize() const; - // Called from DatabaseThread, must be prepared to work on the background thread - void resetAuthorizer(); - void performPolicyChecks(); - - bool performOpenAndVerify(ExceptionCode&); - - void inProgressTransactionCompleted(); void scheduleTransactionCallback(SQLTransaction*); void scheduleTransactionStep(SQLTransaction*, bool immediately = false); - Vector<String> performGetTableNames(); - void performCreationCallback(); - SQLTransactionClient* transactionClient() const; SQLTransactionCoordinator* transactionCoordinator() const; void incrementalVacuumIfNeeded(); private: + class DatabaseOpenTask; + class DatabaseCloseTask; + class DatabaseTransactionTask; + class DatabaseTableNamesTask; + Database(ScriptExecutionContext*, const String& name, const String& expectedVersion, - const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback>); + const String& displayName, unsigned long estimatedSize); - bool openAndVerifyVersion(ExceptionCode&); + bool openAndVerifyVersion(bool setVersionInNewDatabase, ExceptionCode&); + virtual bool performOpenAndVerify(bool setVersionInNewDatabase, ExceptionCode&); + void inProgressTransactionCompleted(); void scheduleTransaction(); + Vector<String> performGetTableNames(); + + static void deliverPendingCallback(void*); + Deque<RefPtr<SQLTransaction> > m_transactionQueue; Mutex m_transactionInProgressMutex; bool m_transactionInProgress; bool m_isTransactionQueueEnabled; - static void deliverPendingCallback(void*); - - RefPtr<ScriptExecutionContext> m_scriptExecutionContext; - RefPtr<SecurityOrigin> m_contextThreadSecurityOrigin; RefPtr<SecurityOrigin> m_databaseThreadSecurityOrigin; - String m_name; - int m_guid; - String m_expectedVersion; - String m_displayName; - unsigned long m_estimatedSize; - String m_filename; bool m_deleted; - - bool m_stopped; - - bool m_opened; - - bool m_new; - - SQLiteDatabase m_sqliteDatabase; - RefPtr<DatabaseAuthorizer> m_databaseAuthorizer; - - RefPtr<DatabaseCallback> m_creationCallback; - -#ifndef NDEBUG - String databaseDebugName() const { return m_contextThreadSecurityOrigin->toString() + "::" + m_name; } -#endif }; } // namespace WebCore diff --git a/WebCore/storage/DatabaseAuthorizer.cpp b/WebCore/storage/DatabaseAuthorizer.cpp index 1ea99d9..d155d6c 100644 --- a/WebCore/storage/DatabaseAuthorizer.cpp +++ b/WebCore/storage/DatabaseAuthorizer.cpp @@ -30,13 +30,19 @@ #include "DatabaseAuthorizer.h" #if ENABLE(DATABASE) -#include "Database.h" #include "PlatformString.h" +#include <wtf/PassRefPtr.h> namespace WebCore { -DatabaseAuthorizer::DatabaseAuthorizer() +PassRefPtr<DatabaseAuthorizer> DatabaseAuthorizer::create(const String& databaseInfoTableName) +{ + return adoptRef(new DatabaseAuthorizer(databaseInfoTableName)); +} + +DatabaseAuthorizer::DatabaseAuthorizer(const String& databaseInfoTableName) : m_securityEnabled(false) + , m_databaseInfoTableName(databaseInfoTableName) { reset(); addWhitelistedFunctions(); @@ -388,7 +394,7 @@ void DatabaseAuthorizer::setReadOnly() m_readOnly = true; } -int DatabaseAuthorizer::denyBasedOnTableName(const String& tableName) +int DatabaseAuthorizer::denyBasedOnTableName(const String& tableName) const { if (!m_securityEnabled) return SQLAuthAllow; @@ -399,7 +405,7 @@ int DatabaseAuthorizer::denyBasedOnTableName(const String& tableName) // equalIgnoringCase(tableName, "sqlite_sequence") || equalIgnoringCase(tableName, Database::databaseInfoTableName())) // return SQLAuthDeny; - if (equalIgnoringCase(tableName, Database::databaseInfoTableName())) + if (equalIgnoringCase(tableName, m_databaseInfoTableName)) return SQLAuthDeny; return SQLAuthAllow; diff --git a/WebCore/storage/DatabaseAuthorizer.h b/WebCore/storage/DatabaseAuthorizer.h index 7da0143..af6e016 100644 --- a/WebCore/storage/DatabaseAuthorizer.h +++ b/WebCore/storage/DatabaseAuthorizer.h @@ -28,10 +28,11 @@ #ifndef DatabaseAuthorizer_h #define DatabaseAuthorizer_h +#include "PlatformString.h" #include "StringHash.h" +#include <wtf/Forward.h> #include <wtf/HashSet.h> -#include <wtf/PassRefPtr.h> -#include <wtf/Threading.h> +#include <wtf/ThreadSafeShared.h> namespace WebCore { @@ -43,7 +44,7 @@ extern const int SQLAuthDeny; class DatabaseAuthorizer : public ThreadSafeShared<DatabaseAuthorizer> { public: - static PassRefPtr<DatabaseAuthorizer> create() { return adoptRef(new DatabaseAuthorizer); } + static PassRefPtr<DatabaseAuthorizer> create(const String& databaseInfoTableName); int createTable(const String& tableName); int createTempTable(const String& tableName); @@ -97,9 +98,9 @@ public: bool hadDeletes() const { return m_hadDeletes; } private: - DatabaseAuthorizer(); + DatabaseAuthorizer(const String& databaseInfoTableName); void addWhitelistedFunctions(); - int denyBasedOnTableName(const String&); + int denyBasedOnTableName(const String&) const; int updateDeletesBasedOnTableName(const String&); bool m_securityEnabled : 1; @@ -108,6 +109,8 @@ private: bool m_readOnly : 1; bool m_hadDeletes : 1; + const String m_databaseInfoTableName; + HashSet<String, CaseFoldingHash> m_whitelistedFunctions; }; diff --git a/WebCore/storage/DatabaseSync.cpp b/WebCore/storage/DatabaseSync.cpp index 0d3bed5..8de9680 100644 --- a/WebCore/storage/DatabaseSync.cpp +++ b/WebCore/storage/DatabaseSync.cpp @@ -31,52 +31,60 @@ #if ENABLE(DATABASE) #include "DatabaseCallback.h" +#include "DatabaseTracker.h" #include "ExceptionCode.h" +#include "Logging.h" #include "SQLTransactionSyncCallback.h" #include "ScriptExecutionContext.h" +#include "SecurityOrigin.h" #include <wtf/PassRefPtr.h> #include <wtf/RefPtr.h> -#include <wtf/StdLibExtras.h> namespace WebCore { -const String& DatabaseSync::databaseInfoTableName() +PassRefPtr<DatabaseSync> DatabaseSync::openDatabaseSync(ScriptExecutionContext* context, const String& name, const String& expectedVersion, const String& displayName, + unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback, ExceptionCode& ec) { - DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__")); - return name; -} + ASSERT(context->isContextThread()); -PassRefPtr<DatabaseSync> DatabaseSync::openDatabaseSync(ScriptExecutionContext*, const String&, const String&, const String&, - unsigned long, PassRefPtr<DatabaseCallback>, ExceptionCode& ec) -{ - // FIXME: uncomment the assert once we use the ScriptExecutionContext* parameter - //ASSERT(context->isContextThread()); + if (!DatabaseTracker::tracker().canEstablishDatabase(context, name, displayName, estimatedSize)) { + LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), context->securityOrigin()->toString().ascii().data()); + return 0; + } + + RefPtr<DatabaseSync> database = adoptRef(new DatabaseSync(context, name, expectedVersion, displayName, estimatedSize)); + + if (!database->performOpenAndVerify(!creationCallback, ec)) { + LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data()); + DatabaseTracker::tracker().removeOpenDatabase(database.get()); + return 0; + } + + DatabaseTracker::tracker().setDatabaseDetails(context->securityOrigin(), name, displayName, estimatedSize); - ec = SECURITY_ERR; - return 0; + if (database->isNew() && creationCallback.get()) { + database->m_expectedVersion = ""; + LOG(StorageAPI, "Invoking the creation callback for database %p\n", database.get()); + creationCallback->handleEvent(context, database.get()); + } + + return database; } DatabaseSync::DatabaseSync(ScriptExecutionContext* context, const String& name, const String& expectedVersion, - const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback> creationCallback) - : m_scriptExecutionContext(context) - , m_name(name.crossThreadString()) - , m_expectedVersion(expectedVersion.crossThreadString()) - , m_displayName(displayName.crossThreadString()) - , m_estimatedSize(estimatedSize) - , m_creationCallback(creationCallback) + const String& displayName, unsigned long estimatedSize) + : AbstractDatabase(context, name, expectedVersion, displayName, estimatedSize) { - ASSERT(context->isContextThread()); } DatabaseSync::~DatabaseSync() { ASSERT(m_scriptExecutionContext->isContextThread()); -} -String DatabaseSync::version() const -{ - ASSERT(m_scriptExecutionContext->isContextThread()); - return String(); + if (opened()) { + DatabaseTracker::tracker().removeOpenDatabase(this); + closeDatabase(); + } } void DatabaseSync::changeVersion(const String&, const String&, PassRefPtr<SQLTransactionSyncCallback>, ExceptionCode&) @@ -89,10 +97,45 @@ void DatabaseSync::transaction(PassRefPtr<SQLTransactionSyncCallback>, bool, Exc ASSERT(m_scriptExecutionContext->isContextThread()); } -ScriptExecutionContext* DatabaseSync::scriptExecutionContext() const +void DatabaseSync::markAsDeletedAndClose() { - ASSERT(m_scriptExecutionContext->isContextThread()); - return m_scriptExecutionContext.get(); + // FIXME: need to do something similar to closeImmediately(), but in a sync way +} + +class CloseSyncDatabaseOnContextThreadTask : public ScriptExecutionContext::Task { +public: + static PassOwnPtr<CloseSyncDatabaseOnContextThreadTask> create(PassRefPtr<DatabaseSync> database) + { + return new CloseSyncDatabaseOnContextThreadTask(database); + } + + virtual void performTask(ScriptExecutionContext*) + { + m_database->closeImmediately(); + } + +private: + CloseSyncDatabaseOnContextThreadTask(PassRefPtr<DatabaseSync> database) + : m_database(database) + { + } + + RefPtr<DatabaseSync> m_database; +}; + +void DatabaseSync::closeImmediately() +{ + if (!m_scriptExecutionContext->isContextThread()) { + m_scriptExecutionContext->postTask(CloseSyncDatabaseOnContextThreadTask::create(this)); + return; + } + + if (!opened()) + return; + + DatabaseTracker::tracker().removeOpenDatabase(this); + + closeDatabase(); } } // namespace WebCore diff --git a/WebCore/storage/DatabaseSync.h b/WebCore/storage/DatabaseSync.h index 321b620..96b4f7d 100644 --- a/WebCore/storage/DatabaseSync.h +++ b/WebCore/storage/DatabaseSync.h @@ -30,48 +30,36 @@ #define DatabaseSync_h #if ENABLE(DATABASE) +#include "AbstractDatabase.h" #include "PlatformString.h" #include <wtf/Forward.h> +#ifndef NDEBUG +#include "SecurityOrigin.h" +#endif namespace WebCore { class DatabaseCallback; class SQLTransactionSyncCallback; class ScriptExecutionContext; - -typedef int ExceptionCode; +class SecurityOrigin; // Instances of this class should be created and used only on the worker's context thread. -class DatabaseSync : public RefCounted<DatabaseSync> { +class DatabaseSync : public AbstractDatabase { public: - ~DatabaseSync(); + virtual ~DatabaseSync(); - // Direct support for the DOM API static PassRefPtr<DatabaseSync> openDatabaseSync(ScriptExecutionContext*, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback>, ExceptionCode&); - String version() const; void changeVersion(const String& oldVersion, const String& newVersion, PassRefPtr<SQLTransactionSyncCallback>, ExceptionCode&); void transaction(PassRefPtr<SQLTransactionSyncCallback>, bool readOnly, ExceptionCode&); - // Internal engine support - ScriptExecutionContext* scriptExecutionContext() const; - - static const String& databaseInfoTableName(); + virtual void markAsDeletedAndClose(); + virtual void closeImmediately(); private: DatabaseSync(ScriptExecutionContext*, const String& name, const String& expectedVersion, - const String& displayName, unsigned long estimatedSize, PassRefPtr<DatabaseCallback>); - - RefPtr<ScriptExecutionContext> m_scriptExecutionContext; - String m_name; - String m_expectedVersion; - String m_displayName; - unsigned long m_estimatedSize; - RefPtr<DatabaseCallback> m_creationCallback; - -#ifndef NDEBUG - String databaseDebugName() const { return String(); } -#endif + const String& displayName, unsigned long estimatedSize); }; } // namespace WebCore diff --git a/WebCore/storage/DatabaseTask.cpp b/WebCore/storage/DatabaseTask.cpp index 8d5f0a8..3526d9d 100644 --- a/WebCore/storage/DatabaseTask.cpp +++ b/WebCore/storage/DatabaseTask.cpp @@ -78,7 +78,6 @@ void DatabaseTask::performTask() m_database->resetAuthorizer(); doPerformTask(); - m_database->performPolicyChecks(); if (m_synchronizer) m_synchronizer->taskCompleted(); @@ -87,21 +86,22 @@ void DatabaseTask::performTask() // *** DatabaseOpenTask *** // Opens the database file and verifies the version matches the expected version. -DatabaseOpenTask::DatabaseOpenTask(Database* database, DatabaseTaskSynchronizer* synchronizer, ExceptionCode& code, bool& success) +Database::DatabaseOpenTask::DatabaseOpenTask(Database* database, bool setVersionInNewDatabase, DatabaseTaskSynchronizer* synchronizer, ExceptionCode& code, bool& success) : DatabaseTask(database, synchronizer) + , m_setVersionInNewDatabase(setVersionInNewDatabase) , m_code(code) , m_success(success) { ASSERT(synchronizer); // A task with output parameters is supposed to be synchronous. } -void DatabaseOpenTask::doPerformTask() +void Database::DatabaseOpenTask::doPerformTask() { - m_success = database()->performOpenAndVerify(m_code); + m_success = database()->performOpenAndVerify(m_setVersionInNewDatabase, m_code); } #ifndef NDEBUG -const char* DatabaseOpenTask::debugTaskName() const +const char* Database::DatabaseOpenTask::debugTaskName() const { return "DatabaseOpenTask"; } @@ -110,19 +110,18 @@ const char* DatabaseOpenTask::debugTaskName() const // *** DatabaseCloseTask *** // Closes the database. -DatabaseCloseTask::DatabaseCloseTask(Database* database, Database::ClosePolicy closePolicy, DatabaseTaskSynchronizer* synchronizer) +Database::DatabaseCloseTask::DatabaseCloseTask(Database* database, DatabaseTaskSynchronizer* synchronizer) : DatabaseTask(database, synchronizer) - , m_closePolicy(closePolicy) { } -void DatabaseCloseTask::doPerformTask() +void Database::DatabaseCloseTask::doPerformTask() { - database()->close(m_closePolicy); + database()->close(); } #ifndef NDEBUG -const char* DatabaseCloseTask::debugTaskName() const +const char* Database::DatabaseCloseTask::debugTaskName() const { return "DatabaseCloseTask"; } @@ -131,24 +130,20 @@ const char* DatabaseCloseTask::debugTaskName() const // *** DatabaseTransactionTask *** // Starts a transaction that will report its results via a callback. -DatabaseTransactionTask::DatabaseTransactionTask(PassRefPtr<SQLTransaction> transaction) +Database::DatabaseTransactionTask::DatabaseTransactionTask(PassRefPtr<SQLTransaction> transaction) : DatabaseTask(transaction->database(), 0) , m_transaction(transaction) { } -DatabaseTransactionTask::~DatabaseTransactionTask() -{ -} - -void DatabaseTransactionTask::doPerformTask() +void Database::DatabaseTransactionTask::doPerformTask() { if (m_transaction->performNextStep()) m_transaction->database()->inProgressTransactionCompleted(); } #ifndef NDEBUG -const char* DatabaseTransactionTask::debugTaskName() const +const char* Database::DatabaseTransactionTask::debugTaskName() const { return "DatabaseTransactionTask"; } @@ -157,20 +152,20 @@ const char* DatabaseTransactionTask::debugTaskName() const // *** DatabaseTableNamesTask *** // Retrieves a list of all tables in the database - for WebInspector support. -DatabaseTableNamesTask::DatabaseTableNamesTask(Database* database, DatabaseTaskSynchronizer* synchronizer, Vector<String>& names) +Database::DatabaseTableNamesTask::DatabaseTableNamesTask(Database* database, DatabaseTaskSynchronizer* synchronizer, Vector<String>& names) : DatabaseTask(database, synchronizer) , m_tableNames(names) { ASSERT(synchronizer); // A task with output parameters is supposed to be synchronous. } -void DatabaseTableNamesTask::doPerformTask() +void Database::DatabaseTableNamesTask::doPerformTask() { m_tableNames = database()->performGetTableNames(); } #ifndef NDEBUG -const char* DatabaseTableNamesTask::debugTaskName() const +const char* Database::DatabaseTableNamesTask::debugTaskName() const { return "DatabaseTableNamesTask"; } diff --git a/WebCore/storage/DatabaseTask.h b/WebCore/storage/DatabaseTask.h index 1a820a5..9673a26 100644 --- a/WebCore/storage/DatabaseTask.h +++ b/WebCore/storage/DatabaseTask.h @@ -60,7 +60,6 @@ private: }; class DatabaseTask : public Noncopyable { - friend class Database; public: virtual ~DatabaseTask(); @@ -83,44 +82,43 @@ private: #endif }; -class DatabaseOpenTask : public DatabaseTask { +class Database::DatabaseOpenTask : public DatabaseTask { public: - static PassOwnPtr<DatabaseOpenTask> create(Database* db, DatabaseTaskSynchronizer* synchronizer, ExceptionCode& code, bool& success) + static PassOwnPtr<DatabaseOpenTask> create(Database* db, bool setVersionInNewDatabase, DatabaseTaskSynchronizer* synchronizer, ExceptionCode& code, bool& success) { - return new DatabaseOpenTask(db, synchronizer, code, success); + return new DatabaseOpenTask(db, setVersionInNewDatabase, synchronizer, code, success); } private: - DatabaseOpenTask(Database*, DatabaseTaskSynchronizer*, ExceptionCode&, bool& success); + DatabaseOpenTask(Database*, bool setVersionInNewDatabase, DatabaseTaskSynchronizer*, ExceptionCode&, bool& success); virtual void doPerformTask(); #ifndef NDEBUG virtual const char* debugTaskName() const; #endif + bool m_setVersionInNewDatabase; ExceptionCode& m_code; bool& m_success; }; -class DatabaseCloseTask : public DatabaseTask { +class Database::DatabaseCloseTask : public DatabaseTask { public: - static PassOwnPtr<DatabaseCloseTask> create(Database* db, Database::ClosePolicy closePolicy, DatabaseTaskSynchronizer* synchronizer) - { - return new DatabaseCloseTask(db, closePolicy, synchronizer); + static PassOwnPtr<DatabaseCloseTask> create(Database* db, DatabaseTaskSynchronizer* synchronizer) + { + return new DatabaseCloseTask(db, synchronizer); } private: - DatabaseCloseTask(Database*, Database::ClosePolicy, DatabaseTaskSynchronizer*); + DatabaseCloseTask(Database*, DatabaseTaskSynchronizer*); virtual void doPerformTask(); #ifndef NDEBUG virtual const char* debugTaskName() const; #endif - - Database::ClosePolicy m_closePolicy; }; -class DatabaseTransactionTask : public DatabaseTask { +class Database::DatabaseTransactionTask : public DatabaseTask { public: // Transaction task is never synchronous, so no 'synchronizer' parameter. static PassOwnPtr<DatabaseTransactionTask> create(PassRefPtr<SQLTransaction> transaction) @@ -130,7 +128,6 @@ public: SQLTransaction* transaction() const { return m_transaction.get(); } - virtual ~DatabaseTransactionTask(); private: DatabaseTransactionTask(PassRefPtr<SQLTransaction>); @@ -142,7 +139,7 @@ private: RefPtr<SQLTransaction> m_transaction; }; -class DatabaseTableNamesTask : public DatabaseTask { +class Database::DatabaseTableNamesTask : public DatabaseTask { public: static PassOwnPtr<DatabaseTableNamesTask> create(Database* db, DatabaseTaskSynchronizer* synchronizer, Vector<String>& names) { diff --git a/WebCore/storage/DatabaseThread.cpp b/WebCore/storage/DatabaseThread.cpp index ec6241c..ae2a6c0 100644 --- a/WebCore/storage/DatabaseThread.cpp +++ b/WebCore/storage/DatabaseThread.cpp @@ -113,14 +113,14 @@ void* DatabaseThread::databaseThread() openSetCopy.swap(m_openDatabaseSet); DatabaseSet::iterator end = openSetCopy.end(); for (DatabaseSet::iterator it = openSetCopy.begin(); it != end; ++it) - (*it)->close(Database::RemoveDatabaseFromContext); + (*it)->close(); } // Detach the thread so its resources are no longer of any concern to anyone else detachThread(m_threadID); DatabaseTaskSynchronizer* cleanupSync = m_cleanupSync; - + // Clear the self refptr, possibly resulting in deletion m_selfRef = 0; diff --git a/WebCore/storage/DatabaseTracker.cpp b/WebCore/storage/DatabaseTracker.cpp index cf56ff6..8d23a4f 100644 --- a/WebCore/storage/DatabaseTracker.cpp +++ b/WebCore/storage/DatabaseTracker.cpp @@ -506,6 +506,12 @@ void DatabaseTracker::addOpenDatabase(AbstractDatabase* database) databaseSet->add(database); LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database); + + Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager()); + if (!originQuotaManager().tracksOrigin(database->securityOrigin())) { + originQuotaManager().trackOrigin(database->securityOrigin()); + originQuotaManager().addDatabase(database->securityOrigin(), database->stringIdentifier(), database->fileName()); + } } MutexLocker lockDatabase(m_databaseGuard); @@ -553,10 +559,10 @@ void DatabaseTracker::removeOpenDatabase(AbstractDatabase* database) m_openDatabaseMap->remove(database->securityOrigin()); delete nameMap; - } - Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager()); - originQuotaManager().removeOrigin(database->securityOrigin()); + Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager()); + originQuotaManager().removeOrigin(database->securityOrigin()); + } } void DatabaseTracker::getOpenDatabases(SecurityOrigin* origin, const String& name, HashSet<RefPtr<AbstractDatabase> >* databases) diff --git a/WebCore/storage/DatabaseTracker.h b/WebCore/storage/DatabaseTracker.h index b1267bc..094ee66 100644 --- a/WebCore/storage/DatabaseTracker.h +++ b/WebCore/storage/DatabaseTracker.h @@ -63,7 +63,8 @@ public: // This singleton will potentially be used from multiple worker threads and the page's context thread simultaneously. To keep this safe, it's // currently using 4 locks. In order to avoid deadlock when taking multiple locks, you must take them in the correct order: // m_databaseGuard before quotaManager if both locks are needed. - // no other lock is taken in the code locked on m_openDatabaseMapGuard. + // m_openDatabaseMapGuard before quotaManager if both locks are needed. + // m_databaseGuard and m_openDatabaseMapGuard currently don't overlap. // notificationMutex() is currently independent of the other locks. bool canEstablishDatabase(ScriptExecutionContext*, const String& name, const String& displayName, unsigned long estimatedSize); diff --git a/WebCore/storage/IDBCallbacks.h b/WebCore/storage/IDBCallbacks.h index c90acc5..37fdc46 100644 --- a/WebCore/storage/IDBCallbacks.h +++ b/WebCore/storage/IDBCallbacks.h @@ -32,6 +32,7 @@ #include "IDBDatabase.h" #include "IDBDatabaseError.h" #include "IDBIndex.h" +#include "IDBKey.h" #include "IDBObjectStore.h" #include "SerializedScriptValue.h" #include <wtf/RefCounted.h> @@ -48,6 +49,7 @@ public: virtual void onSuccess() = 0; // For "null". virtual void onSuccess(PassRefPtr<IDBDatabase>) = 0; virtual void onSuccess(PassRefPtr<IDBIndex>) = 0; + virtual void onSuccess(PassRefPtr<IDBKey>) = 0; virtual void onSuccess(PassRefPtr<IDBObjectStore>) = 0; virtual void onSuccess(PassRefPtr<SerializedScriptValue>) = 0; }; diff --git a/WebCore/storage/IDBKey.cpp b/WebCore/storage/IDBKey.cpp new file mode 100644 index 0000000..36a83a6 --- /dev/null +++ b/WebCore/storage/IDBKey.cpp @@ -0,0 +1,62 @@ +/* + * 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 "config.h" +#include "IDBKey.h" + +#include "SerializedScriptValue.h" + +#if ENABLE(INDEXED_DATABASE) + +namespace WebCore { + +IDBKey::IDBKey() + : m_type(NullType) +{ +} + +IDBKey::~IDBKey() +{ +} + +IDBKey::IDBKey(int32_t number) + : m_type(NumberType) + , m_number(number) +{ +} + +IDBKey::IDBKey(const String& string) + : m_type(StringType) + , m_string(string) +{ +} + +IDBKey::~IDBKey() +{ +} + +} // namespace WebCore + +#endif diff --git a/WebCore/storage/IDBKey.h b/WebCore/storage/IDBKey.h new file mode 100644 index 0000000..0cd205b --- /dev/null +++ b/WebCore/storage/IDBKey.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef IDBKey_h +#define IDBKey_h + +#include "PlatformString.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> + +#if ENABLE(INDEXED_DATABASE) + +namespace WebCore { + +// FIXME: Add dates. +class IDBKey : public RefCounted<IDBKey> { +public: + static PassRefPtr<IDBKey> create() + { + return adoptRef(new IDBKey()); + } + static PassRefPtr<IDBKey> create(int32_t number) + { + return adoptRef(new IDBKey(number)); + } + static PassRefPtr<IDBKey> create(const String& string) + { + return adoptRef(new IDBKey(string)); + } + ~IDBKey() { } + + // In order of the least to the most precident in terms of sort order. + enum Type { + NullType = 0, + StringType, + NumberType + }; + + Type type() const { return m_type; } + + const String& string() const + { + ASSERT(m_type == StringType); + return m_string; + } + + int32_t number() const + { + ASSERT(m_type == NumberType); + return m_number; + } + +private: + IDBKey(); + explicit IDBKey(int32_t); + explicit IDBKey(const String&); + + Type m_type; + String m_string; + int32_t m_number; +}; + +} + +#endif // ENABLE(INDEXED_DATABASE) + +#endif // IDBKey_h diff --git a/WebCore/storage/IDBKey.idl b/WebCore/storage/IDBKey.idl new file mode 100644 index 0000000..04995f3 --- /dev/null +++ b/WebCore/storage/IDBKey.idl @@ -0,0 +1,34 @@ +/* + * 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. + */ + +module storage { + + interface [ + Conditional=INDEXED_DATABASE, + CustomToJS + ] IDBKey { + // This space is intentionally left blank. + }; +} diff --git a/WebCore/storage/IDBKeyTree.h b/WebCore/storage/IDBKeyTree.h new file mode 100644 index 0000000..0e39e77 --- /dev/null +++ b/WebCore/storage/IDBKeyTree.h @@ -0,0 +1,157 @@ +/* + * 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. + */ + +#ifndef IDBKeyTree_h +#define IDBKeyTree_h + +#include "IDBKey.h" +#include <wtf/AVLTree.h> +#include <wtf/Vector.h> + +#if ENABLE(INDEXED_DATABASE) + +namespace WebCore { + +template <typename ValueType> +class IDBKeyTree : public RefCounted<IDBKeyTree<ValueType> > { +public: + static PassRefPtr<IDBKeyTree> create() + { + return adoptRef(new IDBKeyTree()); + } + ~IDBKeyTree(); + + PassRefPtr<ValueType> get(PassRefPtr<IDBKey> key); + void put(PassRefPtr<IDBKey> key, PassRefPtr<ValueType> value); + void remove(PassRefPtr<IDBKey> key); + +private: + struct TreeNode { + RefPtr<ValueType> value; + RefPtr<IDBKey> key; + + TreeNode* less; + TreeNode* greater; + int balanceFactor; + }; + + struct AVLTreeAbstractor { + typedef TreeNode* handle; + typedef size_t size; + typedef IDBKey* key; + + handle get_less(handle h) { return h->less; } + void set_less(handle h, handle lh) { h->less = lh; } + handle get_greater(handle h) { return h->greater; } + void set_greater(handle h, handle gh) { h->greater = gh; } + int get_balance_factor(handle h) { return h->balanceFactor; } + void set_balance_factor(handle h, int bf) { h->balanceFactor = bf; } + + static handle null() { return 0; } + + int compare_key_key(key va, key vb); + int compare_key_node(key k, handle h) { return compare_key_key(k, h->key.get()); } + int compare_node_node(handle h1, handle h2) { return compare_key_key(h1->key.get(), h2->key.get()); } + }; + + IDBKeyTree(); + + typedef WTF::AVLTree<AVLTreeAbstractor> TreeType; + TreeType m_tree; +}; + +template <typename ValueType> +IDBKeyTree<ValueType>::IDBKeyTree() +{ +} + +template <typename ValueType> +IDBKeyTree<ValueType>::~IDBKeyTree() +{ + typename TreeType::Iterator iter; + iter.start_iter_least(m_tree); + for (; *iter; ++iter) + delete *iter; + m_tree.purge(); +} + +template <typename ValueType> +int IDBKeyTree<ValueType>::AVLTreeAbstractor::compare_key_key(key va, key vb) +{ + if (va->type() != vb->type()) + return vb->type() - va->type(); + + switch (va->type()) { + case IDBKey::NullType: + return 0; + case IDBKey::NumberType: + return vb->number() - va->number(); + case IDBKey::StringType: + return codePointCompare(va->string(), vb->string()); + // FIXME: Handle dates. Oh, and test this thoroughly. + default: + ASSERT_NOT_REACHED(); + return 0; + } +} + +template <typename ValueType> +PassRefPtr<ValueType> IDBKeyTree<ValueType>::get(PassRefPtr<IDBKey> prpKey) +{ + RefPtr<IDBKey> key = prpKey; + TreeNode* node = m_tree.search(key.get()); + if (!node) + return 0; + return node->value; +} + + +template <typename ValueType> +void IDBKeyTree<ValueType>::put(PassRefPtr<IDBKey> prpKey, PassRefPtr<ValueType> value) +{ + RefPtr<IDBKey> key = prpKey; + TreeNode* node = m_tree.search(key.get()); + if (!node) { + node = new TreeNode(); + node->key = key.release(); + m_tree.insert(node); + } + node->value = value; +} + +template <typename ValueType> +void IDBKeyTree<ValueType>::remove(PassRefPtr<IDBKey> prpKey) +{ + RefPtr<IDBKey> key = prpKey; + TreeNode* node = m_tree.remove(key.get()); + if (node) + delete node; +} + +} + +#endif // ENABLE(INDEXED_DATABASE) + +#endif // IDBKeyTree_h diff --git a/WebCore/storage/IDBRequest.cpp b/WebCore/storage/IDBRequest.cpp index f0ba25b..7ec8ee6 100644 --- a/WebCore/storage/IDBRequest.cpp +++ b/WebCore/storage/IDBRequest.cpp @@ -85,6 +85,11 @@ void IDBRequest::onSuccess(PassRefPtr<IDBIndex> idbIndex) m_result->set(IDBIndexRequest::create(idbIndex)); } +void IDBRequest::onSuccess(PassRefPtr<IDBKey> idbKey) +{ + ASSERT_NOT_REACHED(); +} + void IDBRequest::onSuccess(PassRefPtr<IDBObjectStore> idbObjectStore) { onEventCommon(); diff --git a/WebCore/storage/IDBRequest.h b/WebCore/storage/IDBRequest.h index 4fb4eed..c763d96 100644 --- a/WebCore/storage/IDBRequest.h +++ b/WebCore/storage/IDBRequest.h @@ -66,6 +66,7 @@ public: virtual void onSuccess(); // For "null". virtual void onSuccess(PassRefPtr<IDBDatabase>); virtual void onSuccess(PassRefPtr<IDBIndex>); + virtual void onSuccess(PassRefPtr<IDBKey>); virtual void onSuccess(PassRefPtr<IDBObjectStore>); virtual void onSuccess(PassRefPtr<SerializedScriptValue>); diff --git a/WebCore/storage/SQLTransaction.cpp b/WebCore/storage/SQLTransaction.cpp index 5a1e2b0..960427b 100644 --- a/WebCore/storage/SQLTransaction.cpp +++ b/WebCore/storage/SQLTransaction.cpp @@ -89,7 +89,7 @@ SQLTransaction::~SQLTransaction() void SQLTransaction::executeSQL(const String& sqlStatement, const Vector<SQLValue>& arguments, PassRefPtr<SQLStatementCallback> callback, PassRefPtr<SQLStatementErrorCallback> callbackError, ExceptionCode& e) { - if (!m_executeSqlAllowed || m_database->stopped()) { + if (!m_executeSqlAllowed || !m_database->opened()) { e = INVALID_STATE_ERR; return; } @@ -149,7 +149,7 @@ const char* SQLTransaction::debugStepName(SQLTransaction::TransactionStepMethod void SQLTransaction::checkAndHandleClosedDatabase() { - if (!m_database->stopped()) + if (m_database->opened()) return; // If the database was stopped, don't do anything and cancel queued work |