2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #include "DatabaseTracker.h"
32 #include "ChromeClient.h"
34 #include "DatabaseTrackerClient.h"
36 #include "FileSystem.h"
38 #include "OriginQuotaManager.h"
40 #include "SecurityOrigin.h"
41 #include "SecurityOriginHash.h"
42 #include "SQLiteStatement.h"
48 OriginQuotaManager& DatabaseTracker::originQuotaManager()
51 ASSERT(m_quotaManager);
52 return *m_quotaManager;
55 DatabaseTracker& DatabaseTracker::tracker()
57 static DatabaseTracker tracker;
61 DatabaseTracker::DatabaseTracker()
63 , m_proposedDatabase(0)
65 , m_thread(currentThread())
70 void DatabaseTracker::setDatabaseDirectoryPath(const String& path)
72 ASSERT(currentThread() == m_thread);
73 ASSERT(!m_database.isOpen());
74 m_databaseDirectoryPath = path;
77 const String& DatabaseTracker::databaseDirectoryPath() const
79 ASSERT(currentThread() == m_thread);
80 return m_databaseDirectoryPath;
83 String DatabaseTracker::trackerDatabasePath() const
85 ASSERT(currentThread() == m_thread);
86 if (m_databaseDirectoryPath.isEmpty())
88 return pathByAppendingComponent(m_databaseDirectoryPath, "Databases.db");
91 void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist)
93 ASSERT(currentThread() == m_thread);
95 if (m_database.isOpen())
98 String databasePath = trackerDatabasePath();
99 if (databasePath.isEmpty())
102 if (!createIfDoesNotExist && !fileExists(databasePath))
105 makeAllDirectories(m_databaseDirectoryPath);
106 if (!m_database.open(databasePath)) {
107 // FIXME: What do do here?
110 if (!m_database.tableExists("Origins")) {
111 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
115 if (!m_database.tableExists("Databases")) {
116 if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
122 bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize)
124 ASSERT(currentThread() == m_thread);
126 // Populate the origins before we establish a database; this guarantees that quotaForOrigin
127 // can run on the database thread later.
130 SecurityOrigin* origin = document->securityOrigin();
132 // Since we're imminently opening a database within this Document's origin, make sure this origin is being tracked by the QuotaTracker
133 // by fetching it's current usage now
134 unsigned long long usage = usageForOrigin(origin);
136 // If a database already exists, ignore the passed-in estimated size and say it's OK.
137 if (hasEntryForDatabase(origin, name))
140 // If the database will fit, allow its creation.
141 unsigned long long requirement = usage + max(1UL, estimatedSize);
142 if (requirement < usage)
143 return false; // If the estimated size is so big it causes an overflow, don't allow creation.
144 if (requirement <= quotaForOrigin(origin))
147 // Give the chrome client a chance to increase the quota.
148 // Temporarily make the details of the proposed database available, so the client can get at them.
149 Page* page = document->page();
152 pair<SecurityOrigin*, DatabaseDetails> details(origin, DatabaseDetails(name, displayName, estimatedSize, 0));
153 m_proposedDatabase = &details;
154 page->chrome()->client()->exceededDatabaseQuota(document->frame(), name);
155 m_proposedDatabase = 0;
157 // If the database will fit now, allow its creation.
158 return requirement <= quotaForOrigin(origin);
161 bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin)
163 ASSERT(currentThread() == m_thread);
165 MutexLocker lockQuotaMap(m_quotaMapGuard);
166 return m_quotaMap->contains(origin);
169 bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier)
171 ASSERT(currentThread() == m_thread);
172 openTrackerDatabase(false);
173 if (!m_database.isOpen())
175 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
177 if (statement.prepare() != SQLResultOk)
180 statement.bindText(1, origin->stringIdentifier());
181 statement.bindText(2, databaseIdentifier);
183 return statement.step() == SQLResultRow;
186 String DatabaseTracker::originPath(SecurityOrigin* origin) const
188 ASSERT(currentThread() == m_thread);
189 if (m_databaseDirectoryPath.isEmpty())
191 return pathByAppendingComponent(m_databaseDirectoryPath, origin->stringIdentifier());
194 String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists)
196 ASSERT(currentThread() == m_thread);
198 if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name)
201 String originIdentifier = origin->stringIdentifier();
202 String originPath = this->originPath(origin);
204 // Make sure the path for this SecurityOrigin exists
205 if (createIfNotExists && !makeAllDirectories(originPath))
208 // See if we have a path for this database yet
209 openTrackerDatabase(false);
210 if (!m_database.isOpen())
212 SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
214 if (statement.prepare() != SQLResultOk)
217 statement.bindText(1, originIdentifier);
218 statement.bindText(2, name);
220 int result = statement.step();
222 if (result == SQLResultRow)
223 return pathByAppendingComponent(originPath, statement.getColumnText(0));
224 if (!createIfNotExists)
227 if (result != SQLResultDone) {
228 LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin->stringIdentifier().ascii().data(), name.ascii().data());
231 statement.finalize();
233 SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");
235 // FIXME: More informative error handling here, even though these steps should never fail
236 if (sequenceStatement.prepare() != SQLResultOk)
238 result = sequenceStatement.step();
240 // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
241 // I can't imagine it'd over overflow
243 if (result == SQLResultRow) {
244 seq = sequenceStatement.getColumnInt64(0);
245 } else if (result != SQLResultDone)
247 sequenceStatement.finalize();
252 filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
253 } while (fileExists(filename));
255 if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
258 // If this origin's quota is being tracked (open handle to a database in this origin), add this new database
259 // to the quota manager now
261 Locker<OriginQuotaManager> locker(originQuotaManager());
262 if (originQuotaManager().tracksOrigin(origin))
263 originQuotaManager().addDatabase(origin, name, filename);
269 void DatabaseTracker::populateOrigins()
274 ASSERT(currentThread() == m_thread);
276 m_quotaMap.set(new QuotaMap);
277 m_quotaManager.set(new OriginQuotaManager);
279 openTrackerDatabase(false);
280 if (!m_database.isOpen())
283 SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
285 if (statement.prepare() != SQLResultOk)
289 while ((result = statement.step()) == SQLResultRow) {
290 RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromIdentifier(statement.getColumnText(0));
291 m_quotaMap->set(origin.get(), statement.getColumnInt64(1));
294 if (result != SQLResultDone)
295 LOG_ERROR("Failed to read in all origins from the database");
298 void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
300 ASSERT(currentThread() == m_thread);
302 MutexLocker lockQuotaMap(m_quotaMapGuard);
303 copyKeysToVector(*m_quotaMap, result);
306 bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector)
308 ASSERT(currentThread() == m_thread);
309 openTrackerDatabase(false);
310 if (!m_database.isOpen())
313 SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
315 if (statement.prepare() != SQLResultOk)
318 statement.bindText(1, origin->stringIdentifier());
321 while ((result = statement.step()) == SQLResultRow)
322 resultVector.append(statement.getColumnText(0));
324 if (result != SQLResultDone) {
325 LOG_ERROR("Failed to retrieve all database names for origin %s", origin->stringIdentifier().ascii().data());
332 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin)
334 ASSERT(currentThread() == m_thread);
336 if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name)
337 return m_proposedDatabase->second;
339 String originIdentifier = origin->stringIdentifier();
341 openTrackerDatabase(false);
342 if (!m_database.isOpen())
343 return DatabaseDetails();
344 SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
345 if (statement.prepare() != SQLResultOk)
346 return DatabaseDetails();
348 statement.bindText(1, originIdentifier);
349 statement.bindText(2, name);
351 int result = statement.step();
352 if (result == SQLResultDone)
353 return DatabaseDetails();
355 if (result != SQLResultRow) {
356 LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
357 return DatabaseDetails();
360 return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
363 void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize)
365 ASSERT(currentThread() == m_thread);
367 String originIdentifier = origin->stringIdentifier();
370 openTrackerDatabase(true);
371 if (!m_database.isOpen())
373 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
374 if (statement.prepare() != SQLResultOk)
377 statement.bindText(1, originIdentifier);
378 statement.bindText(2, name);
380 int result = statement.step();
381 if (result == SQLResultRow)
382 guid = statement.getColumnInt64(0);
383 statement.finalize();
386 if (result != SQLResultDone)
387 LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
389 // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
390 // But since the tracker file is an external resource not under complete control of our code, it's somewhat invalid to make this an ASSERT case
391 // So we'll print an error instead
392 LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker",
393 name.ascii().data(), originIdentifier.ascii().data());
398 SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
399 if (updateStatement.prepare() != SQLResultOk)
402 updateStatement.bindText(1, displayName);
403 updateStatement.bindInt64(2, estimatedSize);
404 updateStatement.bindInt64(3, guid);
406 if (updateStatement.step() != SQLResultDone) {
407 LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
412 m_client->dispatchDidModifyDatabase(origin, name);
415 unsigned long long DatabaseTracker::usageForDatabase(const String& name, SecurityOrigin* origin)
417 ASSERT(currentThread() == m_thread);
418 String path = fullPathForDatabase(origin, name, false);
423 return getFileSize(path, size) ? size : 0;
426 void DatabaseTracker::addOpenDatabase(Database* database)
431 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
433 if (!m_openDatabaseMap)
434 m_openDatabaseMap.set(new DatabaseOriginMap);
436 RefPtr<SecurityOrigin> origin(database->securityOriginCopy());
437 String name(database->stringIdentifier());
439 DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
441 nameMap = new DatabaseNameMap;
442 m_openDatabaseMap->set(origin, nameMap);
445 DatabaseSet* databaseSet = nameMap->get(name);
447 databaseSet = new DatabaseSet;
448 nameMap->set(name, databaseSet);
451 databaseSet->add(database);
453 LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
456 void DatabaseTracker::removeOpenDatabase(Database* database)
461 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
463 if (!m_openDatabaseMap) {
464 ASSERT_NOT_REACHED();
468 RefPtr<SecurityOrigin> origin(database->securityOriginCopy());
469 String name(database->stringIdentifier());
471 DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
473 ASSERT_NOT_REACHED();
477 DatabaseSet* databaseSet = nameMap->get(name);
479 ASSERT_NOT_REACHED();
483 databaseSet->remove(database);
485 LOG(StorageAPI, "Removed open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
487 if (!databaseSet->isEmpty())
490 nameMap->remove(name);
493 if (!nameMap->isEmpty())
496 m_openDatabaseMap->remove(origin);
500 unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
502 ASSERT(currentThread() == m_thread);
503 Locker<OriginQuotaManager> locker(originQuotaManager());
505 // Use the OriginQuotaManager mechanism to calculate the usage
506 if (originQuotaManager().tracksOrigin(origin))
507 return originQuotaManager().diskUsage(origin);
509 // If the OriginQuotaManager doesn't track this origin already, prime it to do so
510 originQuotaManager().trackOrigin(origin);
512 Vector<String> names;
513 databaseNamesForOrigin(origin, names);
515 for (unsigned i = 0; i < names.size(); ++i)
516 originQuotaManager().addDatabase(origin, names[i], fullPathForDatabase(origin, names[i], false));
518 if (!originQuotaManager().tracksOrigin(origin))
520 return originQuotaManager().diskUsage(origin);
523 unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin)
525 ASSERT(currentThread() == m_thread || m_quotaMap);
527 MutexLocker lockQuotaMap(m_quotaMapGuard);
528 return m_quotaMap->get(origin);
531 void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota)
533 ASSERT(currentThread() == m_thread);
534 if (quotaForOrigin(origin) == quota)
537 openTrackerDatabase(true);
538 if (!m_database.isOpen())
542 MutexLocker lockQuotaMap(m_quotaMapGuard);
544 if (!m_quotaMap->contains(origin)) {
545 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
546 if (statement.prepare() != SQLResultOk) {
547 LOG_ERROR("Unable to establish origin %s in the tracker", origin->stringIdentifier().ascii().data());
549 statement.bindText(1, origin->stringIdentifier());
550 statement.bindInt64(2, quota);
552 if (statement.step() != SQLResultDone)
553 LOG_ERROR("Unable to establish origin %s in the tracker", origin->stringIdentifier().ascii().data());
556 SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
557 bool error = statement.prepare() != SQLResultOk;
559 statement.bindInt64(1, quota);
560 statement.bindText(2, origin->stringIdentifier());
562 error = !statement.executeCommand();
566 LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->stringIdentifier().ascii().data());
569 // FIXME: Is it really OK to update the quota in memory if we failed to update it on disk?
570 m_quotaMap->set(origin, quota);
574 m_client->dispatchDidModifyOrigin(origin);
577 bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path)
579 ASSERT(currentThread() == m_thread);
580 openTrackerDatabase(true);
581 if (!m_database.isOpen())
584 // New database should never be added until the origin has been established
585 ASSERT(hasEntryForOrigin(origin));
587 SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
589 if (statement.prepare() != SQLResultOk)
592 statement.bindText(1, origin->stringIdentifier());
593 statement.bindText(2, name);
594 statement.bindText(3, path);
596 if (!statement.executeCommand()) {
597 LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin->stringIdentifier().ascii().data(), m_database.lastErrorMsg());
602 m_client->dispatchDidModifyOrigin(origin);
607 void DatabaseTracker::deleteAllDatabases()
609 ASSERT(currentThread() == m_thread);
611 Vector<RefPtr<SecurityOrigin> > originsCopy;
612 origins(originsCopy);
614 for (unsigned i = 0; i < originsCopy.size(); ++i)
615 deleteOrigin(originsCopy[i].get());
618 void DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
620 ASSERT(currentThread() == m_thread);
621 openTrackerDatabase(false);
622 if (!m_database.isOpen())
625 Vector<String> databaseNames;
626 if (!databaseNamesForOrigin(origin, databaseNames)) {
627 LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->stringIdentifier().ascii().data());
631 for (unsigned i = 0; i < databaseNames.size(); ++i) {
632 if (!deleteDatabaseFile(origin, databaseNames[i])) {
633 LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->stringIdentifier().ascii().data());
638 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
639 if (statement.prepare() != SQLResultOk) {
640 LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
644 statement.bindText(1, origin->stringIdentifier());
646 if (!statement.executeCommand()) {
647 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
651 SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
652 if (originStatement.prepare() != SQLResultOk) {
653 LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin->stringIdentifier().ascii().data());
657 originStatement.bindText(1, origin->stringIdentifier());
659 if (!originStatement.executeCommand()) {
660 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
664 deleteEmptyDirectory(originPath(origin));
666 RefPtr<SecurityOrigin> originPossiblyLastReference = origin;
668 MutexLocker lockQuotaMap(m_quotaMapGuard);
669 m_quotaMap->remove(origin);
671 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
672 originQuotaManager().removeOrigin(origin);
674 // If we removed the last origin, do some additional deletion.
675 if (m_quotaMap->isEmpty()) {
676 if (m_database.isOpen())
678 deleteFile(trackerDatabasePath());
679 deleteEmptyDirectory(m_databaseDirectoryPath);
684 m_client->dispatchDidModifyOrigin(origin);
685 for (unsigned i = 0; i < databaseNames.size(); ++i)
686 m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
690 void DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name)
692 ASSERT(currentThread() == m_thread);
693 openTrackerDatabase(false);
694 if (!m_database.isOpen())
697 if (!deleteDatabaseFile(origin, name)) {
698 LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->stringIdentifier().ascii().data());
702 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
703 if (statement.prepare() != SQLResultOk) {
704 LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin->stringIdentifier().ascii().data());
708 statement.bindText(1, origin->stringIdentifier());
709 statement.bindText(2, name);
711 if (!statement.executeCommand()) {
712 LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin->stringIdentifier().ascii().data());
717 Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
718 originQuotaManager().removeDatabase(origin, name);
722 m_client->dispatchDidModifyOrigin(origin);
723 m_client->dispatchDidModifyDatabase(origin, name);
727 bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name)
729 ASSERT(currentThread() == m_thread);
730 String fullPath = fullPathForDatabase(origin, name, false);
731 if (fullPath.isEmpty())
735 MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
736 if (m_openDatabaseMap) {
737 // There are some open databases, lets check if they are for this origin.
738 DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
739 if (nameMap && nameMap->size()) {
740 // There are some open databases for this origin, lets check
741 // if they are this database by name.
742 DatabaseSet* databaseSet = nameMap->get(name);
743 if (databaseSet && databaseSet->size()) {
744 // We have some database open with this name. Mark them as deleted.
745 DatabaseSet::const_iterator end = databaseSet->end();
746 for (DatabaseSet::const_iterator it = databaseSet->begin(); it != end; ++it)
747 (*it)->markAsDeletedAndClose();
753 return deleteFile(fullPath);
756 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
758 ASSERT(currentThread() == m_thread);
762 static Mutex& notificationMutex()
768 static Vector<pair<SecurityOrigin*, String> >& notificationQueue()
770 static Vector<pair<SecurityOrigin*, String> > queue;
774 void DatabaseTracker::scheduleNotifyDatabaseChanged(SecurityOrigin* origin, const String& name)
776 MutexLocker locker(notificationMutex());
778 notificationQueue().append(pair<SecurityOrigin*, String>(origin, name.copy()));
779 scheduleForNotification();
782 static bool notificationScheduled = false;
784 void DatabaseTracker::scheduleForNotification()
786 ASSERT(!notificationMutex().tryLock());
788 if (!notificationScheduled) {
789 callOnMainThread(DatabaseTracker::notifyDatabasesChanged, 0);
790 notificationScheduled = true;
794 void DatabaseTracker::notifyDatabasesChanged(void*)
796 // Note that if DatabaseTracker ever becomes non-singleton, we'll have to amend this notification
797 // mechanism to include which tracker the notification goes out on as well.
798 DatabaseTracker& theTracker(tracker());
800 Vector<pair<SecurityOrigin*, String> > notifications;
802 MutexLocker locker(notificationMutex());
804 notifications.swap(notificationQueue());
806 notificationScheduled = false;
809 if (!theTracker.m_client)
812 for (unsigned i = 0; i < notifications.size(); ++i)
813 theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first, notifications[i].second);
817 } // namespace WebCore