• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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