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
databaseInfoTableName()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
setIsAvailable(bool available)78 void Database::setIsAvailable(bool available)
79 {
80 isDatabaseAvailable = available;
81 }
82
isAvailable()83 bool Database::isAvailable()
84 {
85 return isDatabaseAvailable;
86 }
87
guidMutex()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;
guidToVersionMap()98 static GuidVersionMap& guidToVersionMap()
99 {
100 DEFINE_STATIC_LOCAL(GuidVersionMap, map, ());
101 return map;
102 }
103
104 // NOTE: Caller must lock guidMutex().
updateGuidVersionMap(int guid,String newVersion)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;
guidToDatabaseMap()121 static GuidDatabaseMap& guidToDatabaseMap()
122 {
123 DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ());
124 return map;
125 }
126
databaseVersionKey()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
openDatabase(ScriptExecutionContext * context,const String & name,const String & expectedVersion,const String & displayName,unsigned long estimatedSize,ExceptionCode & e)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
Database(ScriptExecutionContext * context,const String & name,const String & expectedVersion,const String & displayName,unsigned long estimatedSize)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:
create()210 static PassOwnPtr<DerefContextTask> create()
211 {
212 return new DerefContextTask();
213 }
214
performTask(ScriptExecutionContext * context)215 virtual void performTask(ScriptExecutionContext* context)
216 {
217 context->deref();
218 }
219
isCleanupTask() const220 virtual bool isCleanupTask() const { return true; }
221 };
222
~Database()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
openAndVerifyVersion(ExceptionCode & e)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
retrieveTextResultFromDatabase(SQLiteDatabase & db,const String & query,String & resultString)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
getVersionFromDatabase(String & version)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
setTextValueInDatabase(SQLiteDatabase & db,const String & query,const String & value)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
setVersionInDatabase(const String & version)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
versionMatchesExpected() const325 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
markAsDeletedAndClose()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:
create(PassRefPtr<Database> database)359 static PassOwnPtr<ContextRemoveOpenDatabaseTask> create(PassRefPtr<Database> database)
360 {
361 return new ContextRemoveOpenDatabaseTask(database);
362 }
363
performTask(ScriptExecutionContext * context)364 virtual void performTask(ScriptExecutionContext* context)
365 {
366 context->removeOpenDatabase(m_database.get());
367 DatabaseTracker::tracker().removeOpenDatabase(m_database.get());
368 }
369
isCleanupTask() const370 virtual bool isCleanupTask() const { return true; }
371
372 private:
ContextRemoveOpenDatabaseTask(PassRefPtr<Database> database)373 ContextRemoveOpenDatabaseTask(PassRefPtr<Database> database)
374 : m_database(database)
375 {
376 }
377
378 RefPtr<Database> m_database;
379 };
380
close()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
stop()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
maximumSize() const430 unsigned long long Database::maximumSize() const
431 {
432 return DatabaseTracker::tracker().getMaxSizeForDatabase(this);
433 }
434
disableAuthorizer()435 void Database::disableAuthorizer()
436 {
437 ASSERT(m_databaseAuthorizer);
438 m_databaseAuthorizer->disable();
439 }
440
enableAuthorizer()441 void Database::enableAuthorizer()
442 {
443 ASSERT(m_databaseAuthorizer);
444 m_databaseAuthorizer->enable();
445 }
446
setAuthorizerReadOnly()447 void Database::setAuthorizerReadOnly()
448 {
449 ASSERT(m_databaseAuthorizer);
450 m_databaseAuthorizer->setReadOnly();
451 }
452
guidForOriginAndName(const String & origin,const String & name)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
resetAuthorizer()478 void Database::resetAuthorizer()
479 {
480 if (m_databaseAuthorizer)
481 m_databaseAuthorizer->reset();
482 }
483
performPolicyChecks()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
performOpenAndVerify(ExceptionCode & e)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
changeVersion(const String & oldVersion,const String & newVersion,PassRefPtr<SQLTransactionCallback> callback,PassRefPtr<SQLTransactionErrorCallback> errorCallback,PassRefPtr<VoidCallback> successCallback)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
transaction(PassRefPtr<SQLTransactionCallback> callback,PassRefPtr<SQLTransactionErrorCallback> errorCallback,PassRefPtr<VoidCallback> successCallback,bool readOnly)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
scheduleTransaction()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
scheduleTransactionStep(SQLTransaction * transaction,bool immediately)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:
create(PassRefPtr<SQLTransaction> transaction)635 static PassOwnPtr<DeliverPendingCallbackTask> create(PassRefPtr<SQLTransaction> transaction)
636 {
637 return new DeliverPendingCallbackTask(transaction);
638 }
639
performTask(ScriptExecutionContext *)640 virtual void performTask(ScriptExecutionContext*)
641 {
642 m_transaction->performPendingCallback();
643 }
644
645 private:
DeliverPendingCallbackTask(PassRefPtr<SQLTransaction> transaction)646 DeliverPendingCallbackTask(PassRefPtr<SQLTransaction> transaction)
647 : m_transaction(transaction)
648 {
649 }
650
651 RefPtr<SQLTransaction> m_transaction;
652 };
653
scheduleTransactionCallback(SQLTransaction * transaction)654 void Database::scheduleTransactionCallback(SQLTransaction* transaction)
655 {
656 m_scriptExecutionContext->postTask(DeliverPendingCallbackTask::create(transaction));
657 }
658
performGetTableNames()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
transactionClient() const688 SQLTransactionClient* Database::transactionClient() const
689 {
690 return m_scriptExecutionContext->databaseThread()->transactionClient();
691 }
692
transactionCoordinator() const693 SQLTransactionCoordinator* Database::transactionCoordinator() const
694 {
695 return m_scriptExecutionContext->databaseThread()->transactionCoordinator();
696 }
697
version() const698 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
tableNames()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
setExpectedVersion(const String & version)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
securityOrigin() const731 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
stringIdentifier() const740 String Database::stringIdentifier() const
741 {
742 // Return a deep copy for ref counting thread safety
743 return m_name.threadsafeCopy();
744 }
745
displayName() const746 String Database::displayName() const
747 {
748 // Return a deep copy for ref counting thread safety
749 return m_displayName.threadsafeCopy();
750 }
751
estimatedSize() const752 unsigned long Database::estimatedSize() const
753 {
754 return m_estimatedSize;
755 }
756
fileName() const757 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