1 /* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "config.h" 30 #include "Database.h" 31 32 #include <wtf/StdLibExtras.h> 33 34 #if ENABLE(DATABASE) 35 #include "ChangeVersionWrapper.h" 36 #include "CString.h" 37 #include "DatabaseAuthorizer.h" 38 #include "DatabaseTask.h" 39 #include "DatabaseThread.h" 40 #include "DatabaseTracker.h" 41 #include "Document.h" 42 #include "ExceptionCode.h" 43 #include "Frame.h" 44 #include "InspectorController.h" 45 #include "Logging.h" 46 #include "NotImplemented.h" 47 #include "Page.h" 48 #include "OriginQuotaManager.h" 49 #include "ScriptController.h" 50 #include "SQLiteDatabase.h" 51 #include "SQLiteFileSystem.h" 52 #include "SQLiteStatement.h" 53 #include "SQLResultSet.h" 54 #include "SQLTransactionClient.h" 55 #include "SQLTransactionCoordinator.h" 56 57 #endif // ENABLE(DATABASE) 58 59 #if USE(JSC) 60 #include "JSDOMWindow.h" 61 #endif 62 63 namespace WebCore { 64 65 // If we sleep for more the 30 seconds while blocked on SQLITE_BUSY, give up. 66 static const int maxSqliteBusyWaitTime = 30000; 67 68 const String& Database::databaseInfoTableName() 69 { 70 DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__")); 71 return name; 72 } 73 74 #if ENABLE(DATABASE) 75 76 static bool isDatabaseAvailable = true; 77 78 void Database::setIsAvailable(bool available) 79 { 80 isDatabaseAvailable = available; 81 } 82 83 bool Database::isAvailable() 84 { 85 return isDatabaseAvailable; 86 } 87 88 static Mutex& guidMutex() 89 { 90 // Note: We don't have to use AtomicallyInitializedStatic here because 91 // this function is called once in the constructor on the main thread 92 // before any other threads that call this function are used. 93 DEFINE_STATIC_LOCAL(Mutex, mutex, ()); 94 return mutex; 95 } 96 97 typedef HashMap<int, String> GuidVersionMap; 98 static GuidVersionMap& guidToVersionMap() 99 { 100 DEFINE_STATIC_LOCAL(GuidVersionMap, map, ()); 101 return map; 102 } 103 104 // NOTE: Caller must lock guidMutex(). 105 static inline void updateGuidVersionMap(int guid, String newVersion) 106 { 107 // Ensure the the mutex is locked. 108 ASSERT(!guidMutex().tryLock()); 109 110 // Note: It is not safe to put an empty string into the guidToVersionMap() map. 111 // That's because the map is cross-thread, but empty strings are per-thread. 112 // The copy() function makes a version of the string you can use on the current 113 // thread, but we need a string we can keep in a cross-thread data structure. 114 // FIXME: This is a quite-awkward restriction to have to program with. 115 116 // Map null string to empty string (see comment above). 117 guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy()); 118 } 119 120 typedef HashMap<int, HashSet<Database*>*> GuidDatabaseMap; 121 static GuidDatabaseMap& guidToDatabaseMap() 122 { 123 DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ()); 124 return map; 125 } 126 127 static const String& databaseVersionKey() 128 { 129 DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey")); 130 return key; 131 } 132 133 static int guidForOriginAndName(const String& origin, const String& name); 134 135 PassRefPtr<Database> Database::openDatabase(ScriptExecutionContext* context, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, ExceptionCode& e) 136 { 137 if (!DatabaseTracker::tracker().canEstablishDatabase(context, name, displayName, estimatedSize)) { 138 // FIXME: There should be an exception raised here in addition to returning a null Database object. The question has been raised with the WHATWG. 139 LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), context->securityOrigin()->toString().ascii().data()); 140 return 0; 141 } 142 143 RefPtr<Database> database = adoptRef(new Database(context, name, expectedVersion, displayName, estimatedSize)); 144 145 if (!database->openAndVerifyVersion(e)) { 146 LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data()); 147 context->removeOpenDatabase(database.get()); 148 DatabaseTracker::tracker().removeOpenDatabase(database.get()); 149 return 0; 150 } 151 152 DatabaseTracker::tracker().setDatabaseDetails(context->securityOrigin(), name, displayName, estimatedSize); 153 154 context->setHasOpenDatabases(); 155 #if ENABLE(INSPECTOR) 156 if (context->isDocument()) { 157 Document* document = static_cast<Document*>(context); 158 if (Page* page = document->page()) 159 page->inspectorController()->didOpenDatabase(database.get(), context->securityOrigin()->host(), name, expectedVersion); 160 } 161 #endif 162 163 return database; 164 } 165 166 Database::Database(ScriptExecutionContext* context, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize) 167 : m_transactionInProgress(false) 168 , m_isTransactionQueueEnabled(true) 169 , m_scriptExecutionContext(context) 170 , m_name(name.crossThreadString()) 171 , m_guid(0) 172 , m_expectedVersion(expectedVersion.crossThreadString()) 173 , m_displayName(displayName.crossThreadString()) 174 , m_estimatedSize(estimatedSize) 175 , m_deleted(false) 176 , m_stopped(false) 177 , m_opened(false) 178 { 179 ASSERT(m_scriptExecutionContext.get()); 180 m_mainThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin(); 181 m_databaseThreadSecurityOrigin = m_mainThreadSecurityOrigin->threadsafeCopy(); 182 if (m_name.isNull()) 183 m_name = ""; 184 185 ScriptController::initializeThreading(); 186 187 m_guid = guidForOriginAndName(securityOrigin()->toString(), name); 188 189 { 190 MutexLocker locker(guidMutex()); 191 192 HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid); 193 if (!hashSet) { 194 hashSet = new HashSet<Database*>; 195 guidToDatabaseMap().set(m_guid, hashSet); 196 } 197 198 hashSet->add(this); 199 } 200 201 ASSERT(m_scriptExecutionContext->databaseThread()); 202 m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name); 203 204 DatabaseTracker::tracker().addOpenDatabase(this); 205 context->addOpenDatabase(this); 206 } 207 208 class DerefContextTask : public ScriptExecutionContext::Task { 209 public: 210 static PassOwnPtr<DerefContextTask> create() 211 { 212 return new DerefContextTask(); 213 } 214 215 virtual void performTask(ScriptExecutionContext* context) 216 { 217 context->deref(); 218 } 219 220 virtual bool isCleanupTask() const { return true; } 221 }; 222 223 Database::~Database() 224 { 225 // The reference to the ScriptExecutionContext needs to be cleared on the JavaScript thread. If we're on that thread already, we can just let the RefPtr's destruction do the dereffing. 226 if (!m_scriptExecutionContext->isContextThread()) { 227 m_scriptExecutionContext->postTask(DerefContextTask::create()); 228 m_scriptExecutionContext.release().releaseRef(); 229 } 230 } 231 232 bool Database::openAndVerifyVersion(ExceptionCode& e) 233 { 234 if (!m_scriptExecutionContext->databaseThread()) 235 return false; 236 m_databaseAuthorizer = DatabaseAuthorizer::create(); 237 238 bool success = false; 239 DatabaseTaskSynchronizer synchronizer; 240 OwnPtr<DatabaseOpenTask> task = DatabaseOpenTask::create(this, &synchronizer, e, success); 241 242 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 243 synchronizer.waitForTaskCompletion(); 244 245 return success; 246 } 247 248 249 static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString) 250 { 251 SQLiteStatement statement(db, query); 252 int result = statement.prepare(); 253 254 if (result != SQLResultOk) { 255 LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data()); 256 return false; 257 } 258 259 result = statement.step(); 260 if (result == SQLResultRow) { 261 resultString = statement.getColumnText(0); 262 return true; 263 } else if (result == SQLResultDone) { 264 resultString = String(); 265 return true; 266 } else { 267 LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data()); 268 return false; 269 } 270 } 271 272 bool Database::getVersionFromDatabase(String& version) 273 { 274 DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';")); 275 276 m_databaseAuthorizer->disable(); 277 278 bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version); 279 if (!result) 280 LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data()); 281 282 m_databaseAuthorizer->enable(); 283 284 return result; 285 } 286 287 static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value) 288 { 289 SQLiteStatement statement(db, query); 290 int result = statement.prepare(); 291 292 if (result != SQLResultOk) { 293 LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data()); 294 return false; 295 } 296 297 statement.bindText(1, value); 298 299 result = statement.step(); 300 if (result != SQLResultDone) { 301 LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data()); 302 return false; 303 } 304 305 return true; 306 } 307 308 bool Database::setVersionInDatabase(const String& version) 309 { 310 // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE 311 // clause in the CREATE statement (see Database::performOpenAndVerify()). 312 DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);")); 313 314 m_databaseAuthorizer->disable(); 315 316 bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version); 317 if (!result) 318 LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data()); 319 320 m_databaseAuthorizer->enable(); 321 322 return result; 323 } 324 325 bool Database::versionMatchesExpected() const 326 { 327 if (!m_expectedVersion.isEmpty()) { 328 MutexLocker locker(guidMutex()); 329 return m_expectedVersion == guidToVersionMap().get(m_guid); 330 } 331 332 return true; 333 } 334 335 void Database::markAsDeletedAndClose() 336 { 337 if (m_deleted || !m_scriptExecutionContext->databaseThread()) 338 return; 339 340 LOG(StorageAPI, "Marking %s (%p) as deleted", stringIdentifier().ascii().data(), this); 341 m_deleted = true; 342 343 if (m_scriptExecutionContext->databaseThread()->terminationRequested()) { 344 LOG(StorageAPI, "Database handle %p is on a terminated DatabaseThread, cannot be marked for normal closure\n", this); 345 return; 346 } 347 348 m_scriptExecutionContext->databaseThread()->unscheduleDatabaseTasks(this); 349 350 DatabaseTaskSynchronizer synchronizer; 351 OwnPtr<DatabaseCloseTask> task = DatabaseCloseTask::create(this, &synchronizer); 352 353 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 354 synchronizer.waitForTaskCompletion(); 355 } 356 357 class ContextRemoveOpenDatabaseTask : public ScriptExecutionContext::Task { 358 public: 359 static PassOwnPtr<ContextRemoveOpenDatabaseTask> create(PassRefPtr<Database> database) 360 { 361 return new ContextRemoveOpenDatabaseTask(database); 362 } 363 364 virtual void performTask(ScriptExecutionContext* context) 365 { 366 context->removeOpenDatabase(m_database.get()); 367 DatabaseTracker::tracker().removeOpenDatabase(m_database.get()); 368 } 369 370 virtual bool isCleanupTask() const { return true; } 371 372 private: 373 ContextRemoveOpenDatabaseTask(PassRefPtr<Database> database) 374 : m_database(database) 375 { 376 } 377 378 RefPtr<Database> m_database; 379 }; 380 381 void Database::close() 382 { 383 RefPtr<Database> protect = this; 384 385 if (!m_opened) 386 return; 387 388 ASSERT(m_scriptExecutionContext->databaseThread()); 389 ASSERT(currentThread() == m_scriptExecutionContext->databaseThread()->getThreadID()); 390 m_sqliteDatabase.close(); 391 // Must ref() before calling databaseThread()->recordDatabaseClosed(). 392 m_scriptExecutionContext->databaseThread()->recordDatabaseClosed(this); 393 m_opened = false; 394 395 { 396 MutexLocker locker(guidMutex()); 397 398 HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid); 399 ASSERT(hashSet); 400 ASSERT(hashSet->contains(this)); 401 hashSet->remove(this); 402 if (hashSet->isEmpty()) { 403 guidToDatabaseMap().remove(m_guid); 404 delete hashSet; 405 guidToVersionMap().remove(m_guid); 406 } 407 } 408 409 m_scriptExecutionContext->databaseThread()->unscheduleDatabaseTasks(this); 410 m_scriptExecutionContext->postTask(ContextRemoveOpenDatabaseTask::create(this)); 411 } 412 413 void Database::stop() 414 { 415 // FIXME: The net effect of the following code is to remove all pending transactions and statements, but allow the current statement 416 // to run to completion. In the future we can use the sqlite3_progress_handler or sqlite3_interrupt interfaces to cancel the current 417 // statement in response to close(), as well. 418 419 // 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 420 // a page load or closing the page 421 m_stopped = true; 422 423 { 424 MutexLocker locker(m_transactionInProgressMutex); 425 m_isTransactionQueueEnabled = false; 426 m_transactionInProgress = false; 427 } 428 } 429 430 unsigned long long Database::maximumSize() const 431 { 432 return DatabaseTracker::tracker().getMaxSizeForDatabase(this); 433 } 434 435 void Database::disableAuthorizer() 436 { 437 ASSERT(m_databaseAuthorizer); 438 m_databaseAuthorizer->disable(); 439 } 440 441 void Database::enableAuthorizer() 442 { 443 ASSERT(m_databaseAuthorizer); 444 m_databaseAuthorizer->enable(); 445 } 446 447 void Database::setAuthorizerReadOnly() 448 { 449 ASSERT(m_databaseAuthorizer); 450 m_databaseAuthorizer->setReadOnly(); 451 } 452 453 static int guidForOriginAndName(const String& origin, const String& name) 454 { 455 String stringID; 456 if (origin.endsWith("/")) 457 stringID = origin + name; 458 else 459 stringID = origin + "/" + name; 460 461 // Note: We don't have to use AtomicallyInitializedStatic here because 462 // this function is called once in the constructor on the main thread 463 // before any other threads that call this function are used. 464 DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ()); 465 MutexLocker locker(stringIdentifierMutex); 466 typedef HashMap<String, int> IDGuidMap; 467 DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ()); 468 int guid = stringIdentifierToGUIDMap.get(stringID); 469 if (!guid) { 470 static int currentNewGUID = 1; 471 guid = currentNewGUID++; 472 stringIdentifierToGUIDMap.set(stringID, guid); 473 } 474 475 return guid; 476 } 477 478 void Database::resetAuthorizer() 479 { 480 if (m_databaseAuthorizer) 481 m_databaseAuthorizer->reset(); 482 } 483 484 void Database::performPolicyChecks() 485 { 486 // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates. 487 // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to 488 // increase the size limit 489 490 /* 491 if (m_databaseAuthorizer->lastActionIncreasedSize()) 492 DatabaseTracker::scheduleFileSizeCheckOnMainThread(this); 493 */ 494 495 notImplemented(); 496 } 497 498 bool Database::performOpenAndVerify(ExceptionCode& e) 499 { 500 if (!m_sqliteDatabase.open(m_filename)) { 501 LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data()); 502 e = INVALID_STATE_ERR; 503 return false; 504 } 505 506 ASSERT(m_databaseAuthorizer); 507 m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer); 508 m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime); 509 510 String currentVersion; 511 { 512 MutexLocker locker(guidMutex()); 513 514 GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid); 515 if (entry != guidToVersionMap().end()) { 516 // Map null string to empty string (see updateGuidVersionMap()). 517 currentVersion = entry->second.isNull() ? String("") : entry->second; 518 LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data()); 519 } else { 520 LOG(StorageAPI, "No cached version for guid %i", m_guid); 521 522 if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) { 523 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);")) { 524 LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data()); 525 e = INVALID_STATE_ERR; 526 // Close the handle to the database file. 527 m_sqliteDatabase.close(); 528 return false; 529 } 530 } 531 532 if (!getVersionFromDatabase(currentVersion)) { 533 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data()); 534 e = INVALID_STATE_ERR; 535 // Close the handle to the database file. 536 m_sqliteDatabase.close(); 537 return false; 538 } 539 if (currentVersion.length()) { 540 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data()); 541 } else { 542 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 543 if (!setVersionInDatabase(m_expectedVersion)) { 544 LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data()); 545 e = INVALID_STATE_ERR; 546 // Close the handle to the database file. 547 m_sqliteDatabase.close(); 548 return false; 549 } 550 currentVersion = m_expectedVersion; 551 } 552 553 updateGuidVersionMap(m_guid, currentVersion); 554 } 555 } 556 557 if (currentVersion.isNull()) { 558 LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data()); 559 currentVersion = ""; 560 } 561 562 // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception. 563 // If the expected version is the empty string, then we always return with whatever version of the database we have. 564 if (m_expectedVersion.length() && m_expectedVersion != currentVersion) { 565 LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(), 566 databaseDebugName().ascii().data(), currentVersion.ascii().data()); 567 e = INVALID_STATE_ERR; 568 // Close the handle to the database file. 569 m_sqliteDatabase.close(); 570 return false; 571 } 572 573 // All checks passed and we still have a handle to this database file. 574 // Make sure DatabaseThread closes it when DatabaseThread goes away. 575 m_opened = true; 576 if (m_scriptExecutionContext->databaseThread()) 577 m_scriptExecutionContext->databaseThread()->recordDatabaseOpen(this); 578 579 return true; 580 } 581 582 void Database::changeVersion(const String& oldVersion, const String& newVersion, 583 PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, 584 PassRefPtr<VoidCallback> successCallback) 585 { 586 m_transactionQueue.append(SQLTransaction::create(this, callback, errorCallback, successCallback, ChangeVersionWrapper::create(oldVersion, newVersion))); 587 MutexLocker locker(m_transactionInProgressMutex); 588 if (!m_transactionInProgress) 589 scheduleTransaction(); 590 } 591 592 void Database::transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback, 593 PassRefPtr<VoidCallback> successCallback, bool readOnly) 594 { 595 m_transactionQueue.append(SQLTransaction::create(this, callback, errorCallback, successCallback, 0, readOnly)); 596 MutexLocker locker(m_transactionInProgressMutex); 597 if (!m_transactionInProgress) 598 scheduleTransaction(); 599 } 600 601 void Database::scheduleTransaction() 602 { 603 ASSERT(!m_transactionInProgressMutex.tryLock()); // Locked by caller. 604 RefPtr<SQLTransaction> transaction; 605 606 if (m_isTransactionQueueEnabled && !m_transactionQueue.isEmpty()) { 607 transaction = m_transactionQueue.first(); 608 m_transactionQueue.removeFirst(); 609 } 610 611 if (transaction && m_scriptExecutionContext->databaseThread()) { 612 OwnPtr<DatabaseTransactionTask> task = DatabaseTransactionTask::create(transaction); 613 LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for transaction %p\n", task.get(), task->transaction()); 614 m_transactionInProgress = true; 615 m_scriptExecutionContext->databaseThread()->scheduleTask(task.release()); 616 } else 617 m_transactionInProgress = false; 618 } 619 620 void Database::scheduleTransactionStep(SQLTransaction* transaction, bool immediately) 621 { 622 if (!m_scriptExecutionContext->databaseThread()) 623 return; 624 625 OwnPtr<DatabaseTransactionTask> task = DatabaseTransactionTask::create(transaction); 626 LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for the transaction step\n", task.get()); 627 if (immediately) 628 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 629 else 630 m_scriptExecutionContext->databaseThread()->scheduleTask(task.release()); 631 } 632 633 class DeliverPendingCallbackTask : public ScriptExecutionContext::Task { 634 public: 635 static PassOwnPtr<DeliverPendingCallbackTask> create(PassRefPtr<SQLTransaction> transaction) 636 { 637 return new DeliverPendingCallbackTask(transaction); 638 } 639 640 virtual void performTask(ScriptExecutionContext*) 641 { 642 m_transaction->performPendingCallback(); 643 } 644 645 private: 646 DeliverPendingCallbackTask(PassRefPtr<SQLTransaction> transaction) 647 : m_transaction(transaction) 648 { 649 } 650 651 RefPtr<SQLTransaction> m_transaction; 652 }; 653 654 void Database::scheduleTransactionCallback(SQLTransaction* transaction) 655 { 656 m_scriptExecutionContext->postTask(DeliverPendingCallbackTask::create(transaction)); 657 } 658 659 Vector<String> Database::performGetTableNames() 660 { 661 disableAuthorizer(); 662 663 SQLiteStatement statement(m_sqliteDatabase, "SELECT name FROM sqlite_master WHERE type='table';"); 664 if (statement.prepare() != SQLResultOk) { 665 LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data()); 666 enableAuthorizer(); 667 return Vector<String>(); 668 } 669 670 Vector<String> tableNames; 671 int result; 672 while ((result = statement.step()) == SQLResultRow) { 673 String name = statement.getColumnText(0); 674 if (name != databaseInfoTableName()) 675 tableNames.append(name); 676 } 677 678 enableAuthorizer(); 679 680 if (result != SQLResultDone) { 681 LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data()); 682 return Vector<String>(); 683 } 684 685 return tableNames; 686 } 687 688 SQLTransactionClient* Database::transactionClient() const 689 { 690 return m_scriptExecutionContext->databaseThread()->transactionClient(); 691 } 692 693 SQLTransactionCoordinator* Database::transactionCoordinator() const 694 { 695 return m_scriptExecutionContext->databaseThread()->transactionCoordinator(); 696 } 697 698 String Database::version() const 699 { 700 if (m_deleted) 701 return String(); 702 MutexLocker locker(guidMutex()); 703 return guidToVersionMap().get(m_guid).threadsafeCopy(); 704 } 705 706 Vector<String> Database::tableNames() 707 { 708 // FIXME: Not using threadsafeCopy on these strings looks ok since threads take strict turns 709 // in dealing with them. However, if the code changes, this may not be true anymore. 710 Vector<String> result; 711 if (!m_scriptExecutionContext->databaseThread()) 712 return result; 713 714 DatabaseTaskSynchronizer synchronizer; 715 OwnPtr<DatabaseTableNamesTask> task = DatabaseTableNamesTask::create(this, &synchronizer, result); 716 717 m_scriptExecutionContext->databaseThread()->scheduleImmediateTask(task.release()); 718 synchronizer.waitForTaskCompletion(); 719 720 return result; 721 } 722 723 void Database::setExpectedVersion(const String& version) 724 { 725 m_expectedVersion = version.threadsafeCopy(); 726 // Update the in memory database version map. 727 MutexLocker locker(guidMutex()); 728 updateGuidVersionMap(m_guid, version); 729 } 730 731 SecurityOrigin* Database::securityOrigin() const 732 { 733 if (scriptExecutionContext()->isContextThread()) 734 return m_mainThreadSecurityOrigin.get(); 735 if (currentThread() == m_scriptExecutionContext->databaseThread()->getThreadID()) 736 return m_databaseThreadSecurityOrigin.get(); 737 return 0; 738 } 739 740 String Database::stringIdentifier() const 741 { 742 // Return a deep copy for ref counting thread safety 743 return m_name.threadsafeCopy(); 744 } 745 746 String Database::displayName() const 747 { 748 // Return a deep copy for ref counting thread safety 749 return m_displayName.threadsafeCopy(); 750 } 751 752 unsigned long Database::estimatedSize() const 753 { 754 return m_estimatedSize; 755 } 756 757 String Database::fileName() const 758 { 759 // Return a deep copy for ref counting thread safety 760 return m_filename.threadsafeCopy(); 761 } 762 763 #endif // ENABLE(DATABASE) 764 765 } // namespace WebCore 766