From ca9cb53ed1119a3fd98fafa0972ffeb56dee1c24 Mon Sep 17 00:00:00 2001 From: Steve Block Date: Thu, 8 Jul 2010 12:51:48 +0100 Subject: Merge WebKit at r62496: Initial merge by git Change-Id: Ie3da0770eca22a70a632e3571f31cfabc80facb2 --- WebCore/storage/Database.cpp | 524 ++++--------------------------------------- 1 file changed, 42 insertions(+), 482 deletions(-) (limited to 'WebCore/storage/Database.cpp') 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 #include @@ -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 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*> 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 create(PassRefPtr database) + static PassOwnPtr create(PassRefPtr database, PassRefPtr 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) + DatabaseCreationCallbackTask(PassRefPtr database, PassRefPtr callback) : m_database(database) + , m_creationCallback(callback) { } RefPtr m_database; + RefPtr m_creationCallback; }; PassRefPtr Database::openDatabase(ScriptExecutionContext* context, const String& name, @@ -147,16 +91,14 @@ PassRefPtr 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 = adoptRef(new Database(context, name, expectedVersion, displayName, estimatedSize, creationCallback)); + RefPtr 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::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 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* hashSet = guidToDatabaseMap().get(m_guid); - if (!hashSet) { - hashSet = new HashSet; - 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 task = DatabaseOpenTask::create(this, &synchronizer, e, success); + OwnPtr 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 task = DatabaseCloseTask::create(this, DoNotRemoveDatabaseFromContext, &synchronizer); + OwnPtr 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 create(PassRefPtr 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) - : m_database(database) - { - } - - RefPtr m_database; -}; - -void Database::close(ClosePolicy policy) +void Database::close() { - RefPtr 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* 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 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 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 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 Database::tableNames() { // FIXME: Not using threadsafeCopy on these strings looks ok since threads take strict turns @@ -797,14 +388,6 @@ Vector 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(); -- cgit v1.1