1 /*
2  * Copyright (C) 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved.
3  * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com)
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include "IconDatabase.h"
29 
30 #if ENABLE(ICONDATABASE)
31 
32 #include "AutodrainedPool.h"
33 #include "DocumentLoader.h"
34 #include "FileSystem.h"
35 #include "IconDatabaseClient.h"
36 #include "IconRecord.h"
37 #include "IntSize.h"
38 #include "Logging.h"
39 #include "SQLiteStatement.h"
40 #include "SQLiteTransaction.h"
41 #include "SuddenTermination.h"
42 #include <wtf/CurrentTime.h>
43 #include <wtf/MainThread.h>
44 #include <wtf/StdLibExtras.h>
45 #include <wtf/text/CString.h>
46 
47 // For methods that are meant to support API from the main thread - should not be called internally
48 #define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
49 
50 // For methods that are meant to support the sync thread ONLY
51 #define IS_ICON_SYNC_THREAD() (m_syncThread == currentThread())
52 #define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
53 
54 #if PLATFORM(QT) || PLATFORM(GTK)
55 #define CAN_THEME_URL_ICON
56 #endif
57 
58 namespace WebCore {
59 
60 static int databaseCleanupCounter = 0;
61 
62 // This version number is in the DB and marks the current generation of the schema
63 // Currently, a mismatched schema causes the DB to be wiped and reset.  This isn't
64 // so bad during development but in the future, we would need to write a conversion
65 // function to advance older released schemas to "current"
66 static const int currentDatabaseVersion = 6;
67 
68 // Icons expire once every 4 days
69 static const int iconExpirationTime = 60*60*24*4;
70 
71 static const int updateTimerDelay = 5;
72 
73 static bool checkIntegrityOnOpen = false;
74 
75 #ifndef NDEBUG
urlForLogging(const String & url)76 static String urlForLogging(const String& url)
77 {
78     static unsigned urlTruncationLength = 120;
79 
80     if (url.length() < urlTruncationLength)
81         return url;
82     return url.substring(0, urlTruncationLength) + "...";
83 }
84 #endif
85 
86 class DefaultIconDatabaseClient : public IconDatabaseClient {
87 public:
performImport()88     virtual bool performImport() { return true; }
didImportIconURLForPageURL(const String &)89     virtual void didImportIconURLForPageURL(const String&) { }
didImportIconDataForPageURL(const String &)90     virtual void didImportIconDataForPageURL(const String&) { }
didChangeIconForPageURL(const String &)91     virtual void didChangeIconForPageURL(const String&) { }
didRemoveAllIcons()92     virtual void didRemoveAllIcons() { }
didFinishURLImport()93     virtual void didFinishURLImport() { }
94 };
95 
defaultClient()96 static IconDatabaseClient* defaultClient()
97 {
98     static IconDatabaseClient* defaultClient = new DefaultIconDatabaseClient();
99     return defaultClient;
100 }
101 
pageCanHaveIcon(const String & pageURL)102 static inline bool pageCanHaveIcon(const String& pageURL)
103 {
104     return protocolIsInHTTPFamily(pageURL);
105 }
106 
107 // ************************
108 // *** Main Thread Only ***
109 // ************************
110 
setClient(IconDatabaseClient * client)111 void IconDatabase::setClient(IconDatabaseClient* client)
112 {
113     // We don't allow a null client, because we never null check it anywhere in this code
114     // Also don't allow a client change after the thread has already began
115     // (setting the client should occur before the database is opened)
116     ASSERT(client);
117     ASSERT(!m_syncThreadRunning);
118     if (!client || m_syncThreadRunning)
119         return;
120 
121     m_client = client;
122 }
123 
open(const String & directory,const String & filename)124 bool IconDatabase::open(const String& directory, const String& filename)
125 {
126     ASSERT_NOT_SYNC_THREAD();
127 
128     if (!m_isEnabled)
129         return false;
130 
131     if (isOpen()) {
132         LOG_ERROR("Attempt to reopen the IconDatabase which is already open.  Must close it first.");
133         return false;
134     }
135 
136     m_databaseDirectory = directory.crossThreadString();
137 
138     // Formulate the full path for the database file
139     m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, filename);
140 
141     // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call
142     // completes and m_syncThreadRunning is properly set
143     m_syncLock.lock();
144     m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this, "WebCore: IconDatabase");
145     m_syncThreadRunning = m_syncThread;
146     m_syncLock.unlock();
147     if (!m_syncThread)
148         return false;
149     return true;
150 }
151 
close()152 void IconDatabase::close()
153 {
154     ASSERT_NOT_SYNC_THREAD();
155 
156     if (m_syncThreadRunning) {
157         // Set the flag to tell the sync thread to wrap it up
158         m_threadTerminationRequested = true;
159 
160         // Wake up the sync thread if it's waiting
161         wakeSyncThread();
162 
163         // Wait for the sync thread to terminate
164         waitForThreadCompletion(m_syncThread, 0);
165     }
166 
167     m_syncThreadRunning = false;
168     m_threadTerminationRequested = false;
169     m_removeIconsRequested = false;
170 
171     m_syncDB.close();
172     ASSERT(!isOpen());
173 }
174 
removeAllIcons()175 void IconDatabase::removeAllIcons()
176 {
177     ASSERT_NOT_SYNC_THREAD();
178 
179     if (!isOpen())
180         return;
181 
182     LOG(IconDatabase, "Requesting background thread to remove all icons");
183 
184     // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk
185     {
186         MutexLocker locker(m_urlAndIconLock);
187 
188         // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted
189         // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of
190         HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin();
191         HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end();
192         for (; iter != end; ++iter)
193             (*iter).second->setIconRecord(0);
194 
195         // Clear the iconURL -> IconRecord map
196         m_iconURLToRecordMap.clear();
197 
198         // Clear all in-memory records of things that need to be synced out to disk
199         {
200             MutexLocker locker(m_pendingSyncLock);
201             m_pageURLsPendingSync.clear();
202             m_iconsPendingSync.clear();
203         }
204 
205         // Clear all in-memory records of things that need to be read in from disk
206         {
207             MutexLocker locker(m_pendingReadingLock);
208             m_pageURLsPendingImport.clear();
209             m_pageURLsInterestedInIcons.clear();
210             m_iconsPendingReading.clear();
211             m_loadersPendingDecision.clear();
212         }
213     }
214 
215     m_removeIconsRequested = true;
216     wakeSyncThread();
217 }
218 
synchronousIconForPageURL(const String & pageURLOriginal,const IntSize & size)219 Image* IconDatabase::synchronousIconForPageURL(const String& pageURLOriginal, const IntSize& size)
220 {
221     ASSERT_NOT_SYNC_THREAD();
222 
223     // pageURLOriginal cannot be stored without being deep copied first.
224     // We should go our of our way to only copy it if we have to store it
225 
226     if (!isOpen() || !pageCanHaveIcon(pageURLOriginal))
227         return defaultIcon(size);
228 
229     MutexLocker locker(m_urlAndIconLock);
230 
231     String pageURLCopy; // Creates a null string for easy testing
232 
233     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
234     if (!pageRecord) {
235         pageURLCopy = pageURLOriginal.crossThreadString();
236         pageRecord = getOrCreatePageURLRecord(pageURLCopy);
237     }
238 
239     // If pageRecord is NULL, one of two things is true -
240     // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists
241     // 2 - The initial url import IS complete and this pageURL has no icon
242     if (!pageRecord) {
243         MutexLocker locker(m_pendingReadingLock);
244 
245         // Import is ongoing, there might be an icon.  In this case, register to be notified when the icon comes in
246         // If we ever reach this condition, we know we've already made the pageURL copy
247         if (!m_iconURLImportComplete)
248             m_pageURLsInterestedInIcons.add(pageURLCopy);
249 
250         return 0;
251     }
252 
253     IconRecord* iconRecord = pageRecord->iconRecord();
254 
255     // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon
256     // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so
257     // we can just bail now
258     if (!m_iconURLImportComplete && !iconRecord)
259         return 0;
260 
261     // The only way we should *not* have an icon record is if this pageURL is retained but has no icon yet - make sure of that
262     ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal));
263 
264     if (!iconRecord)
265         return 0;
266 
267     // If it's a new IconRecord object that doesn't have its imageData set yet,
268     // mark it to be read by the background thread
269     if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) {
270         if (pageURLCopy.isNull())
271             pageURLCopy = pageURLOriginal.crossThreadString();
272 
273         MutexLocker locker(m_pendingReadingLock);
274         m_pageURLsInterestedInIcons.add(pageURLCopy);
275         m_iconsPendingReading.add(iconRecord);
276         wakeSyncThread();
277         return 0;
278     }
279 
280     // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off
281     // and isn't actually interested in the image return value
282     if (size == IntSize(0, 0))
283         return 0;
284 
285     // PARANOID DISCUSSION: This method makes some assumptions.  It returns a WebCore::image which the icon database might dispose of at anytime in the future,
286     // and Images aren't ref counted.  So there is no way for the client to guarantee continued existence of the image.
287     // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image
288     // and drop the raw Image*.  On Mac an NSImage, and on windows drawing into an HBITMAP.
289     // The async aspect adds a huge question - what if the image is deleted before the platform specific API has a chance to create its own
290     // representation out of it?
291     // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache.
292     // This is because we make the assumption that anything in memory is newer than whatever is in the database.
293     // So the only time the data will be set from the second thread is when it is INITIALLY being read in from the database, but we would never
294     // delete the image on the secondary thread if the image already exists.
295     return iconRecord->image(size);
296 }
297 
readIconForPageURLFromDisk(const String & pageURL)298 void IconDatabase::readIconForPageURLFromDisk(const String& pageURL)
299 {
300     // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk
301     // if it hasn't already been set in memory.  The special IntSize (0, 0) is a special way of telling
302     // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk.
303     synchronousIconForPageURL(pageURL, IntSize(0, 0));
304 }
305 
synchronousIconURLForPageURL(const String & pageURLOriginal)306 String IconDatabase::synchronousIconURLForPageURL(const String& pageURLOriginal)
307 {
308     ASSERT_NOT_SYNC_THREAD();
309 
310     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
311     // Also, in the case we have a real answer for the caller, we must deep copy that as well
312 
313     if (!isOpen() || !pageCanHaveIcon(pageURLOriginal))
314         return String();
315 
316     MutexLocker locker(m_urlAndIconLock);
317 
318     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
319     if (!pageRecord)
320         pageRecord = getOrCreatePageURLRecord(pageURLOriginal.crossThreadString());
321 
322     // If pageRecord is NULL, one of two things is true -
323     // 1 - The initial url import is incomplete and this pageURL has already been marked to be notified once it is complete if an iconURL exists
324     // 2 - The initial url import IS complete and this pageURL has no icon
325     if (!pageRecord)
326         return String();
327 
328     // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check
329     return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().threadsafeCopy() : String();
330 }
331 
332 #ifdef CAN_THEME_URL_ICON
loadDefaultIconRecord(IconRecord * defaultIconRecord)333 static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
334 {
335      defaultIconRecord->loadImageFromResource("urlIcon");
336 }
337 #else
loadDefaultIconRecord(IconRecord * defaultIconRecord)338 static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
339 {
340     static const unsigned char defaultIconData[] = { 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x03, 0x32, 0x80, 0x00, 0x20, 0x50, 0x38, 0x24, 0x16, 0x0D, 0x07, 0x84, 0x42, 0x61, 0x50, 0xB8,
341         0x64, 0x08, 0x18, 0x0D, 0x0A, 0x0B, 0x84, 0xA2, 0xA1, 0xE2, 0x08, 0x5E, 0x39, 0x28, 0xAF, 0x48, 0x24, 0xD3, 0x53, 0x9A, 0x37, 0x1D, 0x18, 0x0E, 0x8A, 0x4B, 0xD1, 0x38,
342         0xB0, 0x7C, 0x82, 0x07, 0x03, 0x82, 0xA2, 0xE8, 0x6C, 0x2C, 0x03, 0x2F, 0x02, 0x82, 0x41, 0xA1, 0xE2, 0xF8, 0xC8, 0x84, 0x68, 0x6D, 0x1C, 0x11, 0x0A, 0xB7, 0xFA, 0x91,
343         0x6E, 0xD1, 0x7F, 0xAF, 0x9A, 0x4E, 0x87, 0xFB, 0x19, 0xB0, 0xEA, 0x7F, 0xA4, 0x95, 0x8C, 0xB7, 0xF9, 0xA9, 0x0A, 0xA9, 0x7F, 0x8C, 0x88, 0x66, 0x96, 0xD4, 0xCA, 0x69,
344         0x2F, 0x00, 0x81, 0x65, 0xB0, 0x29, 0x90, 0x7C, 0xBA, 0x2B, 0x21, 0x1E, 0x5C, 0xE6, 0xB4, 0xBD, 0x31, 0xB6, 0xE7, 0x7A, 0xBF, 0xDD, 0x6F, 0x37, 0xD3, 0xFD, 0xD8, 0xF2,
345         0xB6, 0xDB, 0xED, 0xAC, 0xF7, 0x03, 0xC5, 0xFE, 0x77, 0x53, 0xB6, 0x1F, 0xE6, 0x24, 0x8B, 0x1D, 0xFE, 0x26, 0x20, 0x9E, 0x1C, 0xE0, 0x80, 0x65, 0x7A, 0x18, 0x02, 0x01,
346         0x82, 0xC5, 0xA0, 0xC0, 0xF1, 0x89, 0xBA, 0x23, 0x30, 0xAD, 0x1F, 0xE7, 0xE5, 0x5B, 0x6D, 0xFE, 0xE7, 0x78, 0x3E, 0x1F, 0xEE, 0x97, 0x8B, 0xE7, 0x37, 0x9D, 0xCF, 0xE7,
347         0x92, 0x8B, 0x87, 0x0B, 0xFC, 0xA0, 0x8E, 0x68, 0x3F, 0xC6, 0x27, 0xA6, 0x33, 0xFC, 0x36, 0x5B, 0x59, 0x3F, 0xC1, 0x02, 0x63, 0x3B, 0x74, 0x00, 0x03, 0x07, 0x0B, 0x61,
348         0x00, 0x20, 0x60, 0xC9, 0x08, 0x00, 0x1C, 0x25, 0x9F, 0xE0, 0x12, 0x8A, 0xD5, 0xFE, 0x6B, 0x4F, 0x35, 0x9F, 0xED, 0xD7, 0x4B, 0xD9, 0xFE, 0x8A, 0x59, 0xB8, 0x1F, 0xEC,
349         0x56, 0xD3, 0xC1, 0xFE, 0x63, 0x4D, 0xF2, 0x83, 0xC6, 0xB6, 0x1B, 0xFC, 0x34, 0x68, 0x61, 0x3F, 0xC1, 0xA6, 0x25, 0xEB, 0xFC, 0x06, 0x58, 0x5C, 0x3F, 0xC0, 0x03, 0xE4,
350         0xC3, 0xFC, 0x04, 0x0F, 0x1A, 0x6F, 0xE0, 0xE0, 0x20, 0xF9, 0x61, 0x7A, 0x02, 0x28, 0x2B, 0xBC, 0x46, 0x25, 0xF3, 0xFC, 0x66, 0x3D, 0x99, 0x27, 0xF9, 0x7E, 0x6B, 0x1D,
351         0xC7, 0xF9, 0x2C, 0x5E, 0x1C, 0x87, 0xF8, 0xC0, 0x4D, 0x9A, 0xE7, 0xF8, 0xDA, 0x51, 0xB2, 0xC1, 0x68, 0xF2, 0x64, 0x1F, 0xE1, 0x50, 0xED, 0x0A, 0x04, 0x23, 0x79, 0x8A,
352         0x7F, 0x82, 0xA3, 0x39, 0x80, 0x7F, 0x80, 0xC2, 0xB1, 0x5E, 0xF7, 0x04, 0x2F, 0xB2, 0x10, 0x02, 0x86, 0x63, 0xC9, 0xCC, 0x07, 0xBF, 0x87, 0xF8, 0x4A, 0x38, 0xAF, 0xC1,
353         0x88, 0xF8, 0x66, 0x1F, 0xE1, 0xD9, 0x08, 0xD4, 0x8F, 0x25, 0x5B, 0x4A, 0x49, 0x97, 0x87, 0x39, 0xFE, 0x25, 0x12, 0x10, 0x68, 0xAA, 0x4A, 0x2F, 0x42, 0x29, 0x12, 0x69,
354         0x9F, 0xE1, 0xC1, 0x00, 0x67, 0x1F, 0xE1, 0x58, 0xED, 0x00, 0x83, 0x23, 0x49, 0x82, 0x7F, 0x81, 0x21, 0xE0, 0xFC, 0x73, 0x21, 0x00, 0x50, 0x7D, 0x2B, 0x84, 0x03, 0x83,
355         0xC2, 0x1B, 0x90, 0x06, 0x69, 0xFE, 0x23, 0x91, 0xAE, 0x50, 0x9A, 0x49, 0x32, 0xC2, 0x89, 0x30, 0xE9, 0x0A, 0xC4, 0xD9, 0xC4, 0x7F, 0x94, 0xA6, 0x51, 0xDE, 0x7F, 0x9D,
356         0x07, 0x89, 0xF6, 0x7F, 0x91, 0x85, 0xCA, 0x88, 0x25, 0x11, 0xEE, 0x50, 0x7C, 0x43, 0x35, 0x21, 0x60, 0xF1, 0x0D, 0x82, 0x62, 0x39, 0x07, 0x2C, 0x20, 0xE0, 0x80, 0x72,
357         0x34, 0x17, 0xA1, 0x80, 0xEE, 0xF0, 0x89, 0x24, 0x74, 0x1A, 0x2C, 0x93, 0xB3, 0x78, 0xCC, 0x52, 0x9D, 0x6A, 0x69, 0x56, 0xBB, 0x0D, 0x85, 0x69, 0xE6, 0x7F, 0x9E, 0x27,
358         0xB9, 0xFD, 0x50, 0x54, 0x47, 0xF9, 0xCC, 0x78, 0x9F, 0x87, 0xF9, 0x98, 0x70, 0xB9, 0xC2, 0x91, 0x2C, 0x6D, 0x1F, 0xE1, 0xE1, 0x00, 0xBF, 0x02, 0xC1, 0xF5, 0x18, 0x84,
359         0x01, 0xE1, 0x48, 0x8C, 0x42, 0x07, 0x43, 0xC9, 0x76, 0x7F, 0x8B, 0x04, 0xE4, 0xDE, 0x35, 0x95, 0xAB, 0xB0, 0xF0, 0x5C, 0x55, 0x23, 0xF9, 0x7E, 0x7E, 0x9F, 0xE4, 0x0C,
360         0xA7, 0x55, 0x47, 0xC7, 0xF9, 0xE6, 0xCF, 0x1F, 0xE7, 0x93, 0x35, 0x52, 0x54, 0x63, 0x19, 0x46, 0x73, 0x1F, 0xE2, 0x61, 0x08, 0xF0, 0x82, 0xE1, 0x80, 0x92, 0xF9, 0x20,
361         0xC0, 0x28, 0x18, 0x0A, 0x05, 0xA1, 0xA2, 0xF8, 0x6E, 0xDB, 0x47, 0x49, 0xFE, 0x3E, 0x17, 0xB6, 0x61, 0x13, 0x1A, 0x29, 0x26, 0xA9, 0xFE, 0x7F, 0x92, 0x70, 0x69, 0xFE,
362         0x4C, 0x2F, 0x55, 0x01, 0xF1, 0x54, 0xD4, 0x35, 0x49, 0x4A, 0x69, 0x59, 0x83, 0x81, 0x58, 0x76, 0x9F, 0xE2, 0x20, 0xD6, 0x4C, 0x9B, 0xA0, 0x48, 0x1E, 0x0B, 0xB7, 0x48,
363         0x58, 0x26, 0x11, 0x06, 0x42, 0xE8, 0xA4, 0x40, 0x17, 0x27, 0x39, 0x00, 0x60, 0x2D, 0xA4, 0xC3, 0x2C, 0x7F, 0x94, 0x56, 0xE4, 0xE1, 0x77, 0x1F, 0xE5, 0xB9, 0xD7, 0x66,
364         0x1E, 0x07, 0xB3, 0x3C, 0x63, 0x1D, 0x35, 0x49, 0x0E, 0x63, 0x2D, 0xA2, 0xF1, 0x12, 0x60, 0x1C, 0xE0, 0xE0, 0x52, 0x1B, 0x8B, 0xAC, 0x38, 0x0E, 0x07, 0x03, 0x60, 0x28,
365         0x1C, 0x0E, 0x87, 0x00, 0xF0, 0x66, 0x27, 0x11, 0xA2, 0xC1, 0x02, 0x5A, 0x1C, 0xE4, 0x21, 0x83, 0x1F, 0x13, 0x86, 0xFA, 0xD2, 0x55, 0x1D, 0xD6, 0x61, 0xBC, 0x77, 0xD3,
366         0xE6, 0x91, 0xCB, 0x4C, 0x90, 0xA6, 0x25, 0xB8, 0x2F, 0x90, 0xC5, 0xA9, 0xCE, 0x12, 0x07, 0x02, 0x91, 0x1B, 0x9F, 0x68, 0x00, 0x16, 0x76, 0x0D, 0xA1, 0x00, 0x08, 0x06,
367         0x03, 0x81, 0xA0, 0x20, 0x1A, 0x0D, 0x06, 0x80, 0x30, 0x24, 0x12, 0x89, 0x20, 0x98, 0x4A, 0x1F, 0x0F, 0x21, 0xA0, 0x9E, 0x36, 0x16, 0xC2, 0x88, 0xE6, 0x48, 0x9B, 0x83,
368         0x31, 0x1C, 0x55, 0x1E, 0x43, 0x59, 0x1A, 0x56, 0x1E, 0x42, 0xF0, 0xFA, 0x4D, 0x1B, 0x9B, 0x08, 0xDC, 0x5B, 0x02, 0xA1, 0x30, 0x7E, 0x3C, 0xEE, 0x5B, 0xA6, 0xDD, 0xB8,
369         0x6D, 0x5B, 0x62, 0xB7, 0xCD, 0xF3, 0x9C, 0xEA, 0x04, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x01,
370         0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0xE0, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00,
371         0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x11, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
372         0x00, 0x08, 0x01, 0x15, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0x16, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x17,
373         0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x29, 0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xE8, 0x01, 0x1B, 0x00, 0x05, 0x00, 0x00,
374         0x00, 0x01, 0x00, 0x00, 0x03, 0xF0, 0x01, 0x1C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
375         0x00, 0x00, 0x01, 0x52, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A,
376         0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 };
377 
378     DEFINE_STATIC_LOCAL(RefPtr<SharedBuffer>, defaultIconBuffer, (SharedBuffer::create(defaultIconData, sizeof(defaultIconData))));
379     defaultIconRecord->setImageData(defaultIconBuffer);
380 }
381 #endif
382 
defaultIcon(const IntSize & size)383 Image* IconDatabase::defaultIcon(const IntSize& size)
384 {
385     ASSERT_NOT_SYNC_THREAD();
386 
387 
388     if (!m_defaultIconRecord) {
389         m_defaultIconRecord = IconRecord::create("urlIcon");
390         loadDefaultIconRecord(m_defaultIconRecord.get());
391     }
392 
393     return m_defaultIconRecord->image(size);
394 }
395 
396 
retainIconForPageURL(const String & pageURLOriginal)397 void IconDatabase::retainIconForPageURL(const String& pageURLOriginal)
398 {
399     ASSERT_NOT_SYNC_THREAD();
400 
401     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
402 
403     if (!isEnabled() || !pageCanHaveIcon(pageURLOriginal))
404         return;
405 
406     MutexLocker locker(m_urlAndIconLock);
407 
408     PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal);
409 
410     String pageURL;
411 
412     if (!record) {
413         pageURL = pageURLOriginal.crossThreadString();
414 
415         record = new PageURLRecord(pageURL);
416         m_pageURLToRecordMap.set(pageURL, record);
417     }
418 
419     if (!record->retain()) {
420         if (pageURL.isNull())
421             pageURL = pageURLOriginal.crossThreadString();
422 
423         // This page just had its retain count bumped from 0 to 1 - Record that fact
424         m_retainedPageURLs.add(pageURL);
425 
426         // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
427         // so we bail here and skip those steps
428         if (!m_iconURLImportComplete)
429             return;
430 
431         MutexLocker locker(m_pendingSyncLock);
432         // If this pageURL waiting to be sync'ed, update the sync record
433         // This saves us in the case where a page was ready to be deleted from the database but was just retained - so theres no need to delete it!
434         if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) {
435             LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data());
436             m_pageURLsPendingSync.set(pageURL, record->snapshot());
437         }
438     }
439 }
440 
releaseIconForPageURL(const String & pageURLOriginal)441 void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal)
442 {
443     ASSERT_NOT_SYNC_THREAD();
444 
445     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
446 
447     if (!isEnabled() || !pageCanHaveIcon(pageURLOriginal))
448         return;
449 
450     MutexLocker locker(m_urlAndIconLock);
451 
452     // Check if this pageURL is actually retained
453     if (!m_retainedPageURLs.contains(pageURLOriginal)) {
454         LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data());
455         return;
456     }
457 
458     // Get its retain count - if it's retained, we'd better have a PageURLRecord for it
459     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
460     ASSERT(pageRecord);
461     LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1);
462     ASSERT(pageRecord->retainCount() > 0);
463 
464     // If it still has a positive retain count, store the new count and bail
465     if (pageRecord->release())
466         return;
467 
468     // This pageRecord has now been fully released.  Do the appropriate cleanup
469     LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data());
470     m_pageURLToRecordMap.remove(pageURLOriginal);
471     m_retainedPageURLs.remove(pageURLOriginal);
472 
473     // Grab the iconRecord for later use (and do a sanity check on it for kicks)
474     IconRecord* iconRecord = pageRecord->iconRecord();
475 
476     ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord));
477 
478     {
479         MutexLocker locker(m_pendingReadingLock);
480 
481         // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results
482         if (!m_iconURLImportComplete)
483             m_pageURLsPendingImport.remove(pageURLOriginal);
484         m_pageURLsInterestedInIcons.remove(pageURLOriginal);
485 
486         // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore
487         if (iconRecord && iconRecord->hasOneRef()) {
488             m_iconURLToRecordMap.remove(iconRecord->iconURL());
489             m_iconsPendingReading.remove(iconRecord);
490         }
491     }
492 
493     // Mark stuff for deletion from the database only if we're not in private browsing
494     if (!m_privateBrowsingEnabled) {
495         MutexLocker locker(m_pendingSyncLock);
496         m_pageURLsPendingSync.set(pageURLOriginal.crossThreadString(), pageRecord->snapshot(true));
497 
498         // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to
499         // be marked for deletion
500         if (iconRecord && iconRecord->hasOneRef())
501             m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
502     }
503 
504     delete pageRecord;
505 
506     if (isOpen())
507         scheduleOrDeferSyncTimer();
508 }
509 
setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal,const String & iconURLOriginal)510 void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal)
511 {
512     ASSERT_NOT_SYNC_THREAD();
513 
514     // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first
515 
516     if (!isOpen() || iconURLOriginal.isEmpty())
517         return;
518 
519     RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : PassRefPtr<SharedBuffer>(0);
520     String iconURL = iconURLOriginal.crossThreadString();
521 
522     Vector<String> pageURLs;
523     {
524         MutexLocker locker(m_urlAndIconLock);
525 
526         // If this icon was pending a read, remove it from that set because this new data should override what is on disk
527         RefPtr<IconRecord> icon = m_iconURLToRecordMap.get(iconURL);
528         if (icon) {
529             MutexLocker locker(m_pendingReadingLock);
530             m_iconsPendingReading.remove(icon.get());
531         } else
532             icon = getOrCreateIconRecord(iconURL);
533 
534         // Update the data and set the time stamp
535         icon->setImageData(data.release());
536         icon->setTimestamp((int)currentTime());
537 
538         // Copy the current retaining pageURLs - if any - to notify them of the change
539         pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end());
540 
541         // Mark the IconRecord as requiring an update to the database only if private browsing is disabled
542         if (!m_privateBrowsingEnabled) {
543             MutexLocker locker(m_pendingSyncLock);
544             m_iconsPendingSync.set(iconURL, icon->snapshot());
545         }
546 
547         if (icon->hasOneRef()) {
548             ASSERT(icon->retainingPageURLs().isEmpty());
549             LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(icon->iconURL()).ascii().data());
550             m_iconURLToRecordMap.remove(icon->iconURL());
551         }
552     }
553 
554     // Send notification out regarding all PageURLs that retain this icon
555     // But not if we're on the sync thread because that implies this mapping
556     // comes from the initial import which we don't want notifications for
557     if (!IS_ICON_SYNC_THREAD()) {
558         // Start the timer to commit this change - or further delay the timer if it was already started
559         scheduleOrDeferSyncTimer();
560 
561         // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
562         // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
563         AutodrainedPool pool(25);
564 
565         for (unsigned i = 0; i < pageURLs.size(); ++i) {
566             LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data());
567             m_client->didChangeIconForPageURL(pageURLs[i]);
568 
569             pool.cycle();
570         }
571     }
572 }
573 
setIconURLForPageURL(const String & iconURLOriginal,const String & pageURLOriginal)574 void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal)
575 {
576     ASSERT_NOT_SYNC_THREAD();
577 
578     // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first
579 
580     ASSERT(!iconURLOriginal.isEmpty());
581 
582     if (!isOpen() || !pageCanHaveIcon(pageURLOriginal))
583         return;
584 
585     String iconURL, pageURL;
586 
587     {
588         MutexLocker locker(m_urlAndIconLock);
589 
590         PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
591 
592         // If the urls already map to each other, bail.
593         // This happens surprisingly often, and seems to cream iBench performance
594         if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal)
595             return;
596 
597         pageURL = pageURLOriginal.crossThreadString();
598         iconURL = iconURLOriginal.crossThreadString();
599 
600         if (!pageRecord) {
601             pageRecord = new PageURLRecord(pageURL);
602             m_pageURLToRecordMap.set(pageURL, pageRecord);
603         }
604 
605         RefPtr<IconRecord> iconRecord = pageRecord->iconRecord();
606 
607         // Otherwise, set the new icon record for this page
608         pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
609 
610         // If the current icon has only a single ref left, it is about to get wiped out.
611         // Remove it from the in-memory records and don't bother reading it in from disk anymore
612         if (iconRecord && iconRecord->hasOneRef()) {
613             ASSERT(iconRecord->retainingPageURLs().size() == 0);
614             LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data());
615             m_iconURLToRecordMap.remove(iconRecord->iconURL());
616             MutexLocker locker(m_pendingReadingLock);
617             m_iconsPendingReading.remove(iconRecord.get());
618         }
619 
620         // And mark this mapping to be added to the database
621         if (!m_privateBrowsingEnabled) {
622             MutexLocker locker(m_pendingSyncLock);
623             m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot());
624 
625             // If the icon is on its last ref, mark it for deletion
626             if (iconRecord && iconRecord->hasOneRef())
627                 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
628         }
629     }
630 
631     // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping
632     // comes from the initial import which we don't want notifications for
633     if (!IS_ICON_SYNC_THREAD()) {
634         // Start the timer to commit this change - or further delay the timer if it was already started
635         scheduleOrDeferSyncTimer();
636 
637         LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data());
638         AutodrainedPool pool;
639         m_client->didChangeIconForPageURL(pageURL);
640     }
641 }
642 
synchronousLoadDecisionForIconURL(const String & iconURL,DocumentLoader * notificationDocumentLoader)643 IconLoadDecision IconDatabase::synchronousLoadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader)
644 {
645     ASSERT_NOT_SYNC_THREAD();
646 
647     if (!isOpen() || iconURL.isEmpty())
648         return IconLoadNo;
649 
650     // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord:
651     // 1 - When we read the icon urls from disk, getting the timeStamp at the same time
652     // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time
653     {
654         MutexLocker locker(m_urlAndIconLock);
655         if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) {
656             LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord");
657             return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo;
658         }
659     }
660 
661     // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now
662     MutexLocker readingLocker(m_pendingReadingLock);
663     if (m_iconURLImportComplete)
664         return IconLoadYes;
665 
666     // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says
667     // "You might be asked to load this later, so flag that"
668     LOG(IconDatabase, "Don't know if we should load %s or not - adding %p to the set of document loaders waiting on a decision", iconURL.ascii().data(), notificationDocumentLoader);
669     if (notificationDocumentLoader)
670         m_loadersPendingDecision.add(notificationDocumentLoader);
671 
672     return IconLoadUnknown;
673 }
674 
synchronousIconDataKnownForIconURL(const String & iconURL)675 bool IconDatabase::synchronousIconDataKnownForIconURL(const String& iconURL)
676 {
677     ASSERT_NOT_SYNC_THREAD();
678 
679     MutexLocker locker(m_urlAndIconLock);
680     if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
681         return icon->imageDataStatus() != ImageDataStatusUnknown;
682 
683     return false;
684 }
685 
setEnabled(bool enabled)686 void IconDatabase::setEnabled(bool enabled)
687 {
688     ASSERT_NOT_SYNC_THREAD();
689 
690     if (!enabled && isOpen())
691         close();
692     m_isEnabled = enabled;
693 }
694 
isEnabled() const695 bool IconDatabase::isEnabled() const
696 {
697     ASSERT_NOT_SYNC_THREAD();
698 
699      return m_isEnabled;
700 }
701 
setPrivateBrowsingEnabled(bool flag)702 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
703 {
704     m_privateBrowsingEnabled = flag;
705 }
706 
isPrivateBrowsingEnabled() const707 bool IconDatabase::isPrivateBrowsingEnabled() const
708 {
709     return m_privateBrowsingEnabled;
710 }
711 
delayDatabaseCleanup()712 void IconDatabase::delayDatabaseCleanup()
713 {
714     ++databaseCleanupCounter;
715     if (databaseCleanupCounter == 1)
716         LOG(IconDatabase, "Database cleanup is now DISABLED");
717 }
718 
allowDatabaseCleanup()719 void IconDatabase::allowDatabaseCleanup()
720 {
721     if (--databaseCleanupCounter < 0)
722         databaseCleanupCounter = 0;
723     if (databaseCleanupCounter == 0)
724         LOG(IconDatabase, "Database cleanup is now ENABLED");
725 }
726 
checkIntegrityBeforeOpening()727 void IconDatabase::checkIntegrityBeforeOpening()
728 {
729     checkIntegrityOnOpen = true;
730 }
731 
pageURLMappingCount()732 size_t IconDatabase::pageURLMappingCount()
733 {
734     MutexLocker locker(m_urlAndIconLock);
735     return m_pageURLToRecordMap.size();
736 }
737 
retainedPageURLCount()738 size_t IconDatabase::retainedPageURLCount()
739 {
740     MutexLocker locker(m_urlAndIconLock);
741     return m_retainedPageURLs.size();
742 }
743 
iconRecordCount()744 size_t IconDatabase::iconRecordCount()
745 {
746     MutexLocker locker(m_urlAndIconLock);
747     return m_iconURLToRecordMap.size();
748 }
749 
iconRecordCountWithData()750 size_t IconDatabase::iconRecordCountWithData()
751 {
752     MutexLocker locker(m_urlAndIconLock);
753     size_t result = 0;
754 
755     HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
756     HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
757 
758     for (; i != end; ++i)
759         result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
760 
761     return result;
762 }
763 
IconDatabase()764 IconDatabase::IconDatabase()
765     : m_syncTimer(this, &IconDatabase::syncTimerFired)
766     , m_syncThreadRunning(false)
767     , m_isEnabled(false)
768     , m_privateBrowsingEnabled(false)
769     , m_threadTerminationRequested(false)
770     , m_removeIconsRequested(false)
771     , m_iconURLImportComplete(false)
772     , m_disabledSuddenTerminationForSyncThread(false)
773     , m_initialPruningComplete(false)
774     , m_client(defaultClient())
775     , m_imported(false)
776     , m_isImportedSet(false)
777 {
778     LOG(IconDatabase, "Creating IconDatabase %p", this);
779     ASSERT(isMainThread());
780 }
781 
~IconDatabase()782 IconDatabase::~IconDatabase()
783 {
784     ASSERT_NOT_REACHED();
785 }
786 
notifyPendingLoadDecisionsOnMainThread(void * context)787 void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context)
788 {
789     static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions();
790 }
791 
notifyPendingLoadDecisions()792 void IconDatabase::notifyPendingLoadDecisions()
793 {
794     ASSERT_NOT_SYNC_THREAD();
795 
796     // This method should only be called upon completion of the initial url import from the database
797     ASSERT(m_iconURLImportComplete);
798     LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
799 
800     HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
801     HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
802 
803     for (; i != end; ++i)
804         if ((*i)->refCount() > 1)
805             (*i)->iconLoadDecisionAvailable();
806 
807     m_loadersPendingDecision.clear();
808 }
809 
wakeSyncThread()810 void IconDatabase::wakeSyncThread()
811 {
812     MutexLocker locker(m_syncLock);
813 
814     if (!m_disabledSuddenTerminationForSyncThread) {
815         m_disabledSuddenTerminationForSyncThread = true;
816         // The following is balanced by the call to enableSuddenTermination in the
817         // syncThreadMainLoop function.
818         // FIXME: It would be better to only disable sudden termination if we have
819         // something to write, not just if we have something to read.
820         disableSuddenTermination();
821     }
822 
823     m_syncCondition.signal();
824 }
825 
scheduleOrDeferSyncTimer()826 void IconDatabase::scheduleOrDeferSyncTimer()
827 {
828     ASSERT_NOT_SYNC_THREAD();
829 
830     if (!m_syncTimer.isActive()) {
831         // The following is balanced by the call to enableSuddenTermination in the
832         // syncTimerFired function.
833         disableSuddenTermination();
834     }
835 
836     m_syncTimer.startOneShot(updateTimerDelay);
837 }
838 
syncTimerFired(Timer<IconDatabase> *)839 void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
840 {
841     ASSERT_NOT_SYNC_THREAD();
842     wakeSyncThread();
843 
844     // The following is balanced by the call to disableSuddenTermination in the
845     // scheduleOrDeferSyncTimer function.
846     enableSuddenTermination();
847 }
848 
849 // ******************
850 // *** Any Thread ***
851 // ******************
852 
isOpen() const853 bool IconDatabase::isOpen() const
854 {
855     MutexLocker locker(m_syncLock);
856     return m_syncDB.isOpen();
857 }
858 
databasePath() const859 String IconDatabase::databasePath() const
860 {
861     MutexLocker locker(m_syncLock);
862     return m_completeDatabasePath.threadsafeCopy();
863 }
864 
defaultDatabaseFilename()865 String IconDatabase::defaultDatabaseFilename()
866 {
867     DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db"));
868     return defaultDatabaseFilename.threadsafeCopy();
869 }
870 
871 // Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import"
getOrCreateIconRecord(const String & iconURL)872 PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL)
873 {
874     // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method
875     ASSERT(!m_urlAndIconLock.tryLock());
876 
877     if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
878         return icon;
879 
880     RefPtr<IconRecord> newIcon = IconRecord::create(iconURL);
881     m_iconURLToRecordMap.set(iconURL, newIcon.get());
882 
883     return newIcon.release();
884 }
885 
886 // This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification
getOrCreatePageURLRecord(const String & pageURL)887 PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL)
888 {
889     // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method
890     ASSERT(!m_urlAndIconLock.tryLock());
891 
892     if (!pageCanHaveIcon(pageURL))
893         return 0;
894 
895     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
896 
897     MutexLocker locker(m_pendingReadingLock);
898     if (!m_iconURLImportComplete) {
899         // If the initial import of all URLs hasn't completed and we have no page record, we assume we *might* know about this later and create a record for it
900         if (!pageRecord) {
901             LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data());
902             pageRecord = new PageURLRecord(pageURL);
903             m_pageURLToRecordMap.set(pageURL, pageRecord);
904         }
905 
906         // If the pageRecord for this page does not have an iconRecord attached to it, then it is a new pageRecord still awaiting the initial import
907         // Mark the URL as "interested in the result of the import" then bail
908         if (!pageRecord->iconRecord()) {
909             m_pageURLsPendingImport.add(pageURL);
910             return 0;
911         }
912     }
913 
914     // We've done the initial import of all URLs known in the database.  If this record doesn't exist now, it never will
915      return pageRecord;
916 }
917 
918 
919 // ************************
920 // *** Sync Thread Only ***
921 // ************************
922 
importIconURLForPageURL(const String & iconURL,const String & pageURL)923 void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL)
924 {
925     ASSERT_ICON_SYNC_THREAD();
926 
927     // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty
928     ASSERT(!iconURL.isEmpty());
929     ASSERT(!pageURL.isEmpty());
930     ASSERT(pageCanHaveIcon(pageURL));
931 
932     setIconURLForPageURLInSQLDatabase(iconURL, pageURL);
933 }
934 
importIconDataForIconURL(PassRefPtr<SharedBuffer> data,const String & iconURL)935 void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL)
936 {
937     ASSERT_ICON_SYNC_THREAD();
938 
939     ASSERT(!iconURL.isEmpty());
940 
941     writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get()));
942 }
943 
shouldStopThreadActivity() const944 bool IconDatabase::shouldStopThreadActivity() const
945 {
946     ASSERT_ICON_SYNC_THREAD();
947 
948     return m_threadTerminationRequested || m_removeIconsRequested;
949 }
950 
iconDatabaseSyncThreadStart(void * vIconDatabase)951 void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase)
952 {
953     IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase);
954 
955     return iconDB->iconDatabaseSyncThread();
956 }
957 
iconDatabaseSyncThread()958 void* IconDatabase::iconDatabaseSyncThread()
959 {
960     // The call to create this thread might not complete before the thread actually starts, so we might fail this ASSERT_ICON_SYNC_THREAD() because the pointer
961     // to our thread structure hasn't been filled in yet.
962     // To fix this, the main thread acquires this lock before creating us, then releases the lock after creation is complete.  A quick lock/unlock cycle here will
963     // prevent us from running before that call completes
964     m_syncLock.lock();
965     m_syncLock.unlock();
966 
967     ASSERT_ICON_SYNC_THREAD();
968 
969     LOG(IconDatabase, "(THREAD) IconDatabase sync thread started");
970 
971 #ifndef NDEBUG
972     double startTime = currentTime();
973 #endif
974 
975     // Need to create the database path if it doesn't already exist
976     makeAllDirectories(m_databaseDirectory);
977 
978     // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies
979     // us to do an integrity check
980     String journalFilename = m_completeDatabasePath + "-journal";
981     if (!checkIntegrityOnOpen) {
982         AutodrainedPool pool;
983         checkIntegrityOnOpen = fileExists(journalFilename);
984     }
985 
986     {
987         MutexLocker locker(m_syncLock);
988         if (!m_syncDB.open(m_completeDatabasePath)) {
989             LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
990             return 0;
991         }
992     }
993 
994     if (shouldStopThreadActivity())
995         return syncThreadMainLoop();
996 
997 #ifndef NDEBUG
998     double timeStamp = currentTime();
999     LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime);
1000 #endif
1001 
1002     performOpenInitialization();
1003     if (shouldStopThreadActivity())
1004         return syncThreadMainLoop();
1005 
1006 #ifndef NDEBUG
1007     double newStamp = currentTime();
1008     LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1009     timeStamp = newStamp;
1010 #endif
1011 
1012     if (!imported()) {
1013         LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure");
1014         SQLiteTransaction importTransaction(m_syncDB);
1015         importTransaction.begin();
1016 
1017         // Commit the transaction only if the import completes (the import should be atomic)
1018         if (m_client->performImport()) {
1019             setImported(true);
1020             importTransaction.commit();
1021         } else {
1022             LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled");
1023             importTransaction.rollback();
1024         }
1025 
1026         if (shouldStopThreadActivity())
1027             return syncThreadMainLoop();
1028 
1029 #ifndef NDEBUG
1030         newStamp = currentTime();
1031         LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1032         timeStamp = newStamp;
1033 #endif
1034     }
1035 
1036     // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories)
1037     // while (currentTime() - timeStamp < 10);
1038 
1039     // Read in URL mappings from the database
1040     LOG(IconDatabase, "(THREAD) Starting iconURL import");
1041     performURLImport();
1042 
1043     if (shouldStopThreadActivity())
1044         return syncThreadMainLoop();
1045 
1046 #ifndef NDEBUG
1047     newStamp = currentTime();
1048     LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds.  Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1049 #endif
1050 
1051     LOG(IconDatabase, "(THREAD) Beginning sync");
1052     return syncThreadMainLoop();
1053 }
1054 
databaseVersionNumber(SQLiteDatabase & db)1055 static int databaseVersionNumber(SQLiteDatabase& db)
1056 {
1057     return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
1058 }
1059 
isValidDatabase(SQLiteDatabase & db)1060 static bool isValidDatabase(SQLiteDatabase& db)
1061 {
1062     // These four tables should always exist in a valid db
1063     if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
1064         return false;
1065 
1066     if (databaseVersionNumber(db) < currentDatabaseVersion) {
1067         LOG(IconDatabase, "DB version is not found or below expected valid version");
1068         return false;
1069     }
1070 
1071     return true;
1072 }
1073 
createDatabaseTables(SQLiteDatabase & db)1074 static void createDatabaseTables(SQLiteDatabase& db)
1075 {
1076     if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
1077         LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1078         db.close();
1079         return;
1080     }
1081     if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
1082         LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1083         db.close();
1084         return;
1085     }
1086     if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) {
1087         LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1088         db.close();
1089         return;
1090     }
1091     if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
1092         LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1093         db.close();
1094         return;
1095     }
1096     if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
1097         LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1098         db.close();
1099         return;
1100     }
1101     if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
1102         LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1103         db.close();
1104         return;
1105     }
1106     if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
1107         LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1108         db.close();
1109         return;
1110     }
1111     if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
1112         LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
1113         db.close();
1114         return;
1115     }
1116 }
1117 
performOpenInitialization()1118 void IconDatabase::performOpenInitialization()
1119 {
1120     ASSERT_ICON_SYNC_THREAD();
1121 
1122     if (!isOpen())
1123         return;
1124 
1125     if (checkIntegrityOnOpen) {
1126         checkIntegrityOnOpen = false;
1127         if (!checkIntegrity()) {
1128             LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
1129 
1130             m_syncDB.close();
1131 
1132             {
1133                 MutexLocker locker(m_syncLock);
1134                 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
1135                 deleteFile(m_completeDatabasePath + "-journal");
1136                 deleteFile(m_completeDatabasePath);
1137             }
1138 
1139             // Reopen the write database, creating it from scratch
1140             if (!m_syncDB.open(m_completeDatabasePath)) {
1141                 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
1142                 return;
1143             }
1144         }
1145     }
1146 
1147     int version = databaseVersionNumber(m_syncDB);
1148 
1149     if (version > currentDatabaseVersion) {
1150         LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
1151         m_syncDB.close();
1152         m_threadTerminationRequested = true;
1153         return;
1154     }
1155 
1156     if (!isValidDatabase(m_syncDB)) {
1157         LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data());
1158         m_syncDB.clearAllTables();
1159         createDatabaseTables(m_syncDB);
1160     }
1161 
1162     // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
1163     if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
1164         LOG_ERROR("SQLite database could not set cache_size");
1165 
1166     // Tell backup software (i.e., Time Machine) to never back up the icon database, because
1167     // it's a large file that changes frequently, thus using a lot of backup disk space, and
1168     // it's unlikely that many users would be upset about it not being backed up. We could
1169     // make this configurable on a per-client basis some day if some clients don't want this.
1170     if (canExcludeFromBackup() && !wasExcludedFromBackup() && excludeFromBackup(m_completeDatabasePath))
1171         setWasExcludedFromBackup();
1172 }
1173 
checkIntegrity()1174 bool IconDatabase::checkIntegrity()
1175 {
1176     ASSERT_ICON_SYNC_THREAD();
1177 
1178     SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
1179     if (integrity.prepare() != SQLResultOk) {
1180         LOG_ERROR("checkIntegrity failed to execute");
1181         return false;
1182     }
1183 
1184     int resultCode = integrity.step();
1185     if (resultCode == SQLResultOk)
1186         return true;
1187 
1188     if (resultCode != SQLResultRow)
1189         return false;
1190 
1191     int columns = integrity.columnCount();
1192     if (columns != 1) {
1193         LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
1194         return false;
1195     }
1196 
1197     String resultText = integrity.getColumnText(0);
1198 
1199     // A successful, no-error integrity check will be "ok" - all other strings imply failure
1200     if (resultText == "ok")
1201         return true;
1202 
1203     LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
1204     return false;
1205 }
1206 
performURLImport()1207 void IconDatabase::performURLImport()
1208 {
1209     ASSERT_ICON_SYNC_THREAD();
1210 
1211     SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
1212 
1213     if (query.prepare() != SQLResultOk) {
1214         LOG_ERROR("Unable to prepare icon url import query");
1215         return;
1216     }
1217 
1218     // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1219     // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1220     AutodrainedPool pool(25);
1221 
1222     int result = query.step();
1223     while (result == SQLResultRow) {
1224         String pageURL = query.getColumnText(0);
1225         String iconURL = query.getColumnText(1);
1226 
1227         {
1228             MutexLocker locker(m_urlAndIconLock);
1229 
1230             PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
1231 
1232             // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
1233             // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
1234             // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
1235             // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
1236             // in - we'll prune it later instead!
1237             if (!pageRecord && databaseCleanupCounter && pageCanHaveIcon(pageURL)) {
1238                 pageRecord = new PageURLRecord(pageURL);
1239                 m_pageURLToRecordMap.set(pageURL, pageRecord);
1240             }
1241 
1242             if (pageRecord) {
1243                 IconRecord* currentIcon = pageRecord->iconRecord();
1244 
1245                 if (!currentIcon || currentIcon->iconURL() != iconURL) {
1246                     pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
1247                     currentIcon = pageRecord->iconRecord();
1248                 }
1249 
1250                 // Regardless, the time stamp from disk still takes precedence.  Until we read this icon from disk, we didn't think we'd seen it before
1251                 // so we marked the timestamp as "now", but it's really much older
1252                 currentIcon->setTimestamp(query.getColumnInt(2));
1253             }
1254         }
1255 
1256         // FIXME: Currently the WebKit API supports 1 type of notification that is sent whenever we get an Icon URL for a Page URL.  We might want to re-purpose it to work for
1257         // getting the actually icon itself also (so each pageurl would get this notification twice) or we might want to add a second type of notification -
1258         // one for the URL and one for the Image itself
1259         // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
1260         {
1261             MutexLocker locker(m_pendingReadingLock);
1262             if (m_pageURLsPendingImport.contains(pageURL)) {
1263                 dispatchDidImportIconURLForPageURLOnMainThread(pageURL);
1264                 m_pageURLsPendingImport.remove(pageURL);
1265 
1266                 pool.cycle();
1267             }
1268         }
1269 
1270         // Stop the import at any time of the thread has been asked to shutdown
1271         if (shouldStopThreadActivity()) {
1272             LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
1273             return;
1274         }
1275 
1276         result = query.step();
1277     }
1278 
1279     if (result != SQLResultDone)
1280         LOG(IconDatabase, "Error reading page->icon url mappings from database");
1281 
1282     // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
1283     // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
1284     Vector<String> urls;
1285     {
1286         MutexLocker locker(m_pendingReadingLock);
1287 
1288         urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
1289         m_pageURLsPendingImport.clear();
1290         m_iconURLImportComplete = true;
1291     }
1292 
1293     Vector<String> urlsToNotify;
1294 
1295     // Loop through the urls pending import
1296     // Remove unretained ones if database cleanup is allowed
1297     // Keep a set of ones that are retained and pending notification
1298     {
1299         MutexLocker locker(m_urlAndIconLock);
1300 
1301         for (unsigned i = 0; i < urls.size(); ++i) {
1302             if (!m_retainedPageURLs.contains(urls[i])) {
1303                 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
1304                 if (record && !databaseCleanupCounter) {
1305                     m_pageURLToRecordMap.remove(urls[i]);
1306                     IconRecord* iconRecord = record->iconRecord();
1307 
1308                     // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
1309                     // reading anything related to it
1310                     if (iconRecord && iconRecord->hasOneRef()) {
1311                         m_iconURLToRecordMap.remove(iconRecord->iconURL());
1312 
1313                         {
1314                             MutexLocker locker(m_pendingReadingLock);
1315                             m_pageURLsInterestedInIcons.remove(urls[i]);
1316                             m_iconsPendingReading.remove(iconRecord);
1317                         }
1318                         {
1319                             MutexLocker locker(m_pendingSyncLock);
1320                             m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
1321                         }
1322                     }
1323 
1324                     delete record;
1325                 }
1326             } else {
1327                 urlsToNotify.append(urls[i]);
1328             }
1329         }
1330     }
1331 
1332     LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size()));
1333     // Now that we don't hold any locks, perform the actual notifications
1334     for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
1335         LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
1336         dispatchDidImportIconURLForPageURLOnMainThread(urlsToNotify[i]);
1337         if (shouldStopThreadActivity())
1338             return;
1339 
1340         pool.cycle();
1341     }
1342 
1343     // Notify the client that the URL import is complete in case it's managing its own pending notifications.
1344     dispatchDidFinishURLImportOnMainThread();
1345 
1346     // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
1347     callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this);
1348 }
1349 
syncThreadMainLoop()1350 void* IconDatabase::syncThreadMainLoop()
1351 {
1352     ASSERT_ICON_SYNC_THREAD();
1353 
1354     bool shouldReenableSuddenTermination = false;
1355 
1356     m_syncLock.lock();
1357 
1358     // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
1359     while (!m_threadTerminationRequested) {
1360         m_syncLock.unlock();
1361 
1362 #ifndef NDEBUG
1363         double timeStamp = currentTime();
1364 #endif
1365         LOG(IconDatabase, "(THREAD) Main work loop starting");
1366 
1367         // If we should remove all icons, do it now.  This is an uninteruptible procedure that we will always do before quitting if it is requested
1368         if (m_removeIconsRequested) {
1369             removeAllIconsOnThread();
1370             m_removeIconsRequested = false;
1371         }
1372 
1373         // Then, if the thread should be quitting, quit now!
1374         if (m_threadTerminationRequested)
1375             break;
1376 
1377         bool didAnyWork = true;
1378         while (didAnyWork) {
1379             bool didWrite = writeToDatabase();
1380             if (shouldStopThreadActivity())
1381                 break;
1382 
1383             didAnyWork = readFromDatabase();
1384             if (shouldStopThreadActivity())
1385                 break;
1386 
1387             // Prune unretained icons after the first time we sync anything out to the database
1388             // This way, pruning won't be the only operation we perform to the database by itself
1389             // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
1390             // or if private browsing is enabled
1391             // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
1392             // has asked to delay pruning
1393             static bool prunedUnretainedIcons = false;
1394             if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
1395 #ifndef NDEBUG
1396                 double time = currentTime();
1397 #endif
1398                 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
1399 
1400                 pruneUnretainedIcons();
1401 
1402                 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
1403 
1404                 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
1405                 // to mark prunedUnretainedIcons true because we're about to terminate anyway
1406                 prunedUnretainedIcons = true;
1407             }
1408 
1409             didAnyWork = didAnyWork || didWrite;
1410             if (shouldStopThreadActivity())
1411                 break;
1412         }
1413 
1414 #ifndef NDEBUG
1415         double newstamp = currentTime();
1416         LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
1417 #endif
1418 
1419         m_syncLock.lock();
1420 
1421         // There is some condition that is asking us to stop what we're doing now and handle a special case
1422         // This is either removing all icons, or shutting down the thread to quit the app
1423         // We handle those at the top of this main loop so continue to jump back up there
1424         if (shouldStopThreadActivity())
1425             continue;
1426 
1427         if (shouldReenableSuddenTermination) {
1428             // The following is balanced by the call to disableSuddenTermination in the
1429             // wakeSyncThread function. Any time we wait on the condition, we also have
1430             // to enableSuddenTermation, after doing the next batch of work.
1431             ASSERT(m_disabledSuddenTerminationForSyncThread);
1432             enableSuddenTermination();
1433             m_disabledSuddenTerminationForSyncThread = false;
1434         }
1435 
1436         m_syncCondition.wait(m_syncLock);
1437 
1438         shouldReenableSuddenTermination = true;
1439     }
1440 
1441     m_syncLock.unlock();
1442 
1443     // Thread is terminating at this point
1444     cleanupSyncThread();
1445 
1446     if (shouldReenableSuddenTermination) {
1447         // The following is balanced by the call to disableSuddenTermination in the
1448         // wakeSyncThread function. Any time we wait on the condition, we also have
1449         // to enableSuddenTermation, after doing the next batch of work.
1450         ASSERT(m_disabledSuddenTerminationForSyncThread);
1451         enableSuddenTermination();
1452         m_disabledSuddenTerminationForSyncThread = false;
1453     }
1454 
1455     return 0;
1456 }
1457 
readFromDatabase()1458 bool IconDatabase::readFromDatabase()
1459 {
1460     ASSERT_ICON_SYNC_THREAD();
1461 
1462 #ifndef NDEBUG
1463     double timeStamp = currentTime();
1464 #endif
1465 
1466     bool didAnyWork = false;
1467 
1468     // We'll make a copy of the sets of things that need to be read.  Then we'll verify at the time of updating the record that it still wants to be updated
1469     // This way we won't hold the lock for a long period of time
1470     Vector<IconRecord*> icons;
1471     {
1472         MutexLocker locker(m_pendingReadingLock);
1473         icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
1474     }
1475 
1476     // Keep track of icons we actually read to notify them of the new icon
1477     HashSet<String> urlsToNotify;
1478 
1479     for (unsigned i = 0; i < icons.size(); ++i) {
1480         didAnyWork = true;
1481         RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
1482 
1483         // Verify this icon still wants to be read from disk
1484         {
1485             MutexLocker urlLocker(m_urlAndIconLock);
1486             {
1487                 MutexLocker readLocker(m_pendingReadingLock);
1488 
1489                 if (m_iconsPendingReading.contains(icons[i])) {
1490                     // Set the new data
1491                     icons[i]->setImageData(imageData.release());
1492 
1493                     // Remove this icon from the set that needs to be read
1494                     m_iconsPendingReading.remove(icons[i]);
1495 
1496                     // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
1497                     // We want to find the intersection of these two sets to notify them
1498                     // Check the sizes of these two sets to minimize the number of iterations
1499                     const HashSet<String>* outerHash;
1500                     const HashSet<String>* innerHash;
1501 
1502                     if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
1503                         outerHash = &m_pageURLsInterestedInIcons;
1504                         innerHash = &(icons[i]->retainingPageURLs());
1505                     } else {
1506                         innerHash = &m_pageURLsInterestedInIcons;
1507                         outerHash = &(icons[i]->retainingPageURLs());
1508                     }
1509 
1510                     HashSet<String>::const_iterator iter = outerHash->begin();
1511                     HashSet<String>::const_iterator end = outerHash->end();
1512                     for (; iter != end; ++iter) {
1513                         if (innerHash->contains(*iter)) {
1514                             LOG(IconDatabase, "%s is interesting in the icon we just read.  Adding it to the list and removing it from the interested set", urlForLogging(*iter).ascii().data());
1515                             urlsToNotify.add(*iter);
1516                         }
1517 
1518                         // If we ever get to the point were we've seen every url interested in this icon, break early
1519                         if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1520                             break;
1521                     }
1522 
1523                     // We don't need to notify a PageURL twice, so all the ones we're about to notify can be removed from the interested set
1524                     if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1525                         m_pageURLsInterestedInIcons.clear();
1526                     else {
1527                         iter = urlsToNotify.begin();
1528                         end = urlsToNotify.end();
1529                         for (; iter != end; ++iter)
1530                             m_pageURLsInterestedInIcons.remove(*iter);
1531                     }
1532                 }
1533             }
1534         }
1535 
1536         if (shouldStopThreadActivity())
1537             return didAnyWork;
1538 
1539         // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1540         // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1541         AutodrainedPool pool(25);
1542 
1543         // Now that we don't hold any locks, perform the actual notifications
1544         HashSet<String>::iterator iter = urlsToNotify.begin();
1545         HashSet<String>::iterator end = urlsToNotify.end();
1546         for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
1547             LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
1548             dispatchDidImportIconDataForPageURLOnMainThread(*iter);
1549             if (shouldStopThreadActivity())
1550                 return didAnyWork;
1551 
1552             pool.cycle();
1553         }
1554 
1555         LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
1556         urlsToNotify.clear();
1557 
1558         if (shouldStopThreadActivity())
1559             return didAnyWork;
1560     }
1561 
1562     LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
1563 
1564     return didAnyWork;
1565 }
1566 
writeToDatabase()1567 bool IconDatabase::writeToDatabase()
1568 {
1569     ASSERT_ICON_SYNC_THREAD();
1570 
1571 #ifndef NDEBUG
1572     double timeStamp = currentTime();
1573 #endif
1574 
1575     bool didAnyWork = false;
1576 
1577     // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
1578     // we'll pick it up on the next pass.  This greatly simplifies the locking strategy for this method and remains cohesive with changes
1579     // asked for by the database on the main thread
1580     {
1581         MutexLocker locker(m_urlAndIconLock);
1582         Vector<IconSnapshot> iconSnapshots;
1583         Vector<PageURLSnapshot> pageSnapshots;
1584         {
1585             MutexLocker locker(m_pendingSyncLock);
1586 
1587             iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
1588             m_iconsPendingSync.clear();
1589 
1590             pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
1591             m_pageURLsPendingSync.clear();
1592         }
1593 
1594         if (iconSnapshots.size() || pageSnapshots.size())
1595             didAnyWork = true;
1596 
1597         SQLiteTransaction syncTransaction(m_syncDB);
1598         syncTransaction.begin();
1599 
1600         for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
1601             writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
1602             LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL()).ascii().data(), iconSnapshots[i].timestamp());
1603         }
1604 
1605         for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
1606             // If the icon URL is empty, this page is meant to be deleted
1607             // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
1608             if (pageSnapshots[i].iconURL().isEmpty())
1609                 removePageURLFromSQLDatabase(pageSnapshots[i].pageURL());
1610             else
1611                 setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL(), pageSnapshots[i].pageURL());
1612             LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL()).ascii().data());
1613         }
1614 
1615         syncTransaction.commit();
1616     }
1617 
1618     // Check to make sure there are no dangling PageURLs - If there are, we want to output one log message but not spam the console potentially every few seconds
1619     if (didAnyWork)
1620         checkForDanglingPageURLs(false);
1621 
1622     LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
1623 
1624     return didAnyWork;
1625 }
1626 
pruneUnretainedIcons()1627 void IconDatabase::pruneUnretainedIcons()
1628 {
1629     ASSERT_ICON_SYNC_THREAD();
1630 
1631     if (!isOpen())
1632         return;
1633 
1634     // This method should only be called once per run
1635     ASSERT(!m_initialPruningComplete);
1636 
1637     // This method relies on having read in all page URLs from the database earlier.
1638     ASSERT(m_iconURLImportComplete);
1639 
1640     // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
1641     Vector<int64_t> pageIDsToDelete;
1642 
1643     SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
1644     pageSQL.prepare();
1645 
1646     int result;
1647     while ((result = pageSQL.step()) == SQLResultRow) {
1648         MutexLocker locker(m_urlAndIconLock);
1649         if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1)))
1650             pageIDsToDelete.append(pageSQL.getColumnInt64(0));
1651     }
1652 
1653     if (result != SQLResultDone)
1654         LOG_ERROR("Error reading PageURL table from on-disk DB");
1655     pageSQL.finalize();
1656 
1657     // Delete page URLs that were in the table, but not in our retain count set.
1658     size_t numToDelete = pageIDsToDelete.size();
1659     if (numToDelete) {
1660         SQLiteTransaction pruningTransaction(m_syncDB);
1661         pruningTransaction.begin();
1662 
1663         SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
1664         pageDeleteSQL.prepare();
1665         for (size_t i = 0; i < numToDelete; ++i) {
1666 #if OS(WINDOWS)
1667             LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
1668 #else
1669             LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
1670 #endif
1671             pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
1672             int result = pageDeleteSQL.step();
1673             if (result != SQLResultDone)
1674 #if OS(WINDOWS)
1675                 LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
1676 #else
1677                 LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
1678 #endif
1679             pageDeleteSQL.reset();
1680 
1681             // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
1682             // finish the rest later (hopefully)
1683             if (shouldStopThreadActivity()) {
1684                 pruningTransaction.commit();
1685                 return;
1686             }
1687         }
1688         pruningTransaction.commit();
1689         pageDeleteSQL.finalize();
1690     }
1691 
1692     // Deleting unreferenced icons from the Icon tables has to be atomic -
1693     // If the user quits while these are taking place, they might have to wait.  Thankfully this will rarely be an issue
1694     // A user on a network home directory with a wildly inconsistent database might see quite a pause...
1695 
1696     SQLiteTransaction pruningTransaction(m_syncDB);
1697     pruningTransaction.begin();
1698 
1699     // Wipe Icons that aren't retained
1700     if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1701         LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
1702     if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1703         LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
1704 
1705     pruningTransaction.commit();
1706 
1707     checkForDanglingPageURLs(true);
1708 
1709     m_initialPruningComplete = true;
1710 }
1711 
checkForDanglingPageURLs(bool pruneIfFound)1712 void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
1713 {
1714     ASSERT_ICON_SYNC_THREAD();
1715 
1716     // This check can be relatively expensive so we don't do it in a release build unless the caller has asked us to prune any dangling
1717     // entries.  We also don't want to keep performing this check and reporting this error if it has already found danglers before so we
1718     // keep track of whether we've found any.  We skip the check in the release build pretending to have already found danglers already.
1719 #ifndef NDEBUG
1720     static bool danglersFound = true;
1721 #else
1722     static bool danglersFound = false;
1723 #endif
1724 
1725     if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
1726         danglersFound = true;
1727         LOG(IconDatabase, "Dangling PageURL entries found");
1728         if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
1729             LOG(IconDatabase, "Unable to prune dangling PageURLs");
1730     }
1731 }
1732 
removeAllIconsOnThread()1733 void IconDatabase::removeAllIconsOnThread()
1734 {
1735     ASSERT_ICON_SYNC_THREAD();
1736 
1737     LOG(IconDatabase, "Removing all icons on the sync thread");
1738 
1739     // Delete all the prepared statements so they can start over
1740     deleteAllPreparedStatements();
1741 
1742     // To reset the on-disk database, we'll wipe all its tables then vacuum it
1743     // This is easier and safer than closing it, deleting the file, and recreating from scratch
1744     m_syncDB.clearAllTables();
1745     m_syncDB.runVacuumCommand();
1746     createDatabaseTables(m_syncDB);
1747 
1748     LOG(IconDatabase, "Dispatching notification that we removed all icons");
1749     dispatchDidRemoveAllIconsOnMainThread();
1750 }
1751 
deleteAllPreparedStatements()1752 void IconDatabase::deleteAllPreparedStatements()
1753 {
1754     ASSERT_ICON_SYNC_THREAD();
1755 
1756     m_setIconIDForPageURLStatement.clear();
1757     m_removePageURLStatement.clear();
1758     m_getIconIDForIconURLStatement.clear();
1759     m_getImageDataForIconURLStatement.clear();
1760     m_addIconToIconInfoStatement.clear();
1761     m_addIconToIconDataStatement.clear();
1762     m_getImageDataStatement.clear();
1763     m_deletePageURLsForIconURLStatement.clear();
1764     m_deleteIconFromIconInfoStatement.clear();
1765     m_deleteIconFromIconDataStatement.clear();
1766     m_updateIconInfoStatement.clear();
1767     m_updateIconDataStatement.clear();
1768     m_setIconInfoStatement.clear();
1769     m_setIconDataStatement.clear();
1770 }
1771 
cleanupSyncThread()1772 void* IconDatabase::cleanupSyncThread()
1773 {
1774     ASSERT_ICON_SYNC_THREAD();
1775 
1776 #ifndef NDEBUG
1777     double timeStamp = currentTime();
1778 #endif
1779 
1780     // If the removeIcons flag is set, remove all icons from the db.
1781     if (m_removeIconsRequested)
1782         removeAllIconsOnThread();
1783 
1784     // Sync remaining icons out
1785     LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
1786     writeToDatabase();
1787 
1788     // Close the database
1789     MutexLocker locker(m_syncLock);
1790 
1791     m_databaseDirectory = String();
1792     m_completeDatabasePath = String();
1793     deleteAllPreparedStatements();
1794     m_syncDB.close();
1795 
1796 #ifndef NDEBUG
1797     LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
1798 #endif
1799 
1800     m_syncThreadRunning = false;
1801     return 0;
1802 }
1803 
imported()1804 bool IconDatabase::imported()
1805 {
1806     ASSERT_ICON_SYNC_THREAD();
1807 
1808     if (m_isImportedSet)
1809         return m_imported;
1810 
1811     SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
1812     if (query.prepare() != SQLResultOk) {
1813         LOG_ERROR("Unable to prepare imported statement");
1814         return false;
1815     }
1816 
1817     int result = query.step();
1818     if (result == SQLResultRow)
1819         result = query.getColumnInt(0);
1820     else {
1821         if (result != SQLResultDone)
1822             LOG_ERROR("imported statement failed");
1823         result = 0;
1824     }
1825 
1826     m_isImportedSet = true;
1827     return m_imported = result;
1828 }
1829 
setImported(bool import)1830 void IconDatabase::setImported(bool import)
1831 {
1832     ASSERT_ICON_SYNC_THREAD();
1833 
1834     m_imported = import;
1835     m_isImportedSet = true;
1836 
1837     String queryString = import ?
1838         "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
1839         "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
1840 
1841     SQLiteStatement query(m_syncDB, queryString);
1842 
1843     if (query.prepare() != SQLResultOk) {
1844         LOG_ERROR("Unable to prepare set imported statement");
1845         return;
1846     }
1847 
1848     if (query.step() != SQLResultDone)
1849         LOG_ERROR("set imported statement failed");
1850 }
1851 
1852 // readySQLiteStatement() handles two things
1853 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade.  This happens when the user
1854 //     switches to and from private browsing
1855 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
readySQLiteStatement(OwnPtr<SQLiteStatement> & statement,SQLiteDatabase & db,const String & str)1856 inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
1857 {
1858     if (statement && (statement->database() != &db || statement->isExpired())) {
1859         if (statement->isExpired())
1860             LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
1861         statement.clear();
1862     }
1863     if (!statement) {
1864         statement = adoptPtr(new SQLiteStatement(db, str));
1865         if (statement->prepare() != SQLResultOk)
1866             LOG_ERROR("Preparing statement %s failed", str.ascii().data());
1867     }
1868 }
1869 
setIconURLForPageURLInSQLDatabase(const String & iconURL,const String & pageURL)1870 void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
1871 {
1872     ASSERT_ICON_SYNC_THREAD();
1873 
1874     int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1875 
1876     if (!iconID)
1877         iconID = addIconURLToSQLDatabase(iconURL);
1878 
1879     if (!iconID) {
1880         LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
1881         ASSERT(false);
1882         return;
1883     }
1884 
1885     setIconIDForPageURLInSQLDatabase(iconID, pageURL);
1886 }
1887 
setIconIDForPageURLInSQLDatabase(int64_t iconID,const String & pageURL)1888 void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
1889 {
1890     ASSERT_ICON_SYNC_THREAD();
1891 
1892     readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
1893     m_setIconIDForPageURLStatement->bindText(1, pageURL);
1894     m_setIconIDForPageURLStatement->bindInt64(2, iconID);
1895 
1896     int result = m_setIconIDForPageURLStatement->step();
1897     if (result != SQLResultDone) {
1898         ASSERT(false);
1899         LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
1900     }
1901 
1902     m_setIconIDForPageURLStatement->reset();
1903 }
1904 
removePageURLFromSQLDatabase(const String & pageURL)1905 void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
1906 {
1907     ASSERT_ICON_SYNC_THREAD();
1908 
1909     readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
1910     m_removePageURLStatement->bindText(1, pageURL);
1911 
1912     if (m_removePageURLStatement->step() != SQLResultDone)
1913         LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
1914 
1915     m_removePageURLStatement->reset();
1916 }
1917 
1918 
getIconIDForIconURLFromSQLDatabase(const String & iconURL)1919 int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
1920 {
1921     ASSERT_ICON_SYNC_THREAD();
1922 
1923     readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
1924     m_getIconIDForIconURLStatement->bindText(1, iconURL);
1925 
1926     int64_t result = m_getIconIDForIconURLStatement->step();
1927     if (result == SQLResultRow)
1928         result = m_getIconIDForIconURLStatement->getColumnInt64(0);
1929     else {
1930         if (result != SQLResultDone)
1931             LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1932         result = 0;
1933     }
1934 
1935     m_getIconIDForIconURLStatement->reset();
1936     return result;
1937 }
1938 
addIconURLToSQLDatabase(const String & iconURL)1939 int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
1940 {
1941     ASSERT_ICON_SYNC_THREAD();
1942 
1943     // There would be a transaction here to make sure these two inserts are atomic
1944     // In practice the only caller of this method is always wrapped in a transaction itself so placing another
1945     // here is unnecessary
1946 
1947     readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
1948     m_addIconToIconInfoStatement->bindText(1, iconURL);
1949 
1950     int result = m_addIconToIconInfoStatement->step();
1951     m_addIconToIconInfoStatement->reset();
1952     if (result != SQLResultDone) {
1953         LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
1954         return 0;
1955     }
1956     int64_t iconID = m_syncDB.lastInsertRowID();
1957 
1958     readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
1959     m_addIconToIconDataStatement->bindInt64(1, iconID);
1960 
1961     result = m_addIconToIconDataStatement->step();
1962     m_addIconToIconDataStatement->reset();
1963     if (result != SQLResultDone) {
1964         LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
1965         return 0;
1966     }
1967 
1968     return iconID;
1969 }
1970 
getImageDataForIconURLFromSQLDatabase(const String & iconURL)1971 PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
1972 {
1973     ASSERT_ICON_SYNC_THREAD();
1974 
1975     RefPtr<SharedBuffer> imageData;
1976 
1977     readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
1978     m_getImageDataForIconURLStatement->bindText(1, iconURL);
1979 
1980     int result = m_getImageDataForIconURLStatement->step();
1981     if (result == SQLResultRow) {
1982         Vector<char> data;
1983         m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
1984         imageData = SharedBuffer::create(data.data(), data.size());
1985     } else if (result != SQLResultDone)
1986         LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1987 
1988     m_getImageDataForIconURLStatement->reset();
1989 
1990     return imageData.release();
1991 }
1992 
removeIconFromSQLDatabase(const String & iconURL)1993 void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
1994 {
1995     ASSERT_ICON_SYNC_THREAD();
1996 
1997     if (iconURL.isEmpty())
1998         return;
1999 
2000     // There would be a transaction here to make sure these removals are atomic
2001     // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
2002 
2003     // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
2004     // icon is marked to be added then marked for removal before it is ever written to disk.  No big deal, early return
2005     int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
2006     if (!iconID)
2007         return;
2008 
2009     readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
2010     m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
2011 
2012     if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
2013         LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2014 
2015     readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
2016     m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
2017 
2018     if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
2019         LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2020 
2021     readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
2022     m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
2023 
2024     if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
2025         LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2026 
2027     m_deletePageURLsForIconURLStatement->reset();
2028     m_deleteIconFromIconInfoStatement->reset();
2029     m_deleteIconFromIconDataStatement->reset();
2030 }
2031 
writeIconSnapshotToSQLDatabase(const IconSnapshot & snapshot)2032 void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
2033 {
2034     ASSERT_ICON_SYNC_THREAD();
2035 
2036     if (snapshot.iconURL().isEmpty())
2037         return;
2038 
2039     // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
2040     if (!snapshot.timestamp() && !snapshot.data()) {
2041         LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL()).ascii().data());
2042         removeIconFromSQLDatabase(snapshot.iconURL());
2043         return;
2044     }
2045 
2046     // There would be a transaction here to make sure these removals are atomic
2047     // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
2048 
2049     // Get the iconID for this url
2050     int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL());
2051 
2052     // If there is already an iconID in place, update the database.
2053     // Otherwise, insert new records
2054     if (iconID) {
2055         readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
2056         m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp());
2057         m_updateIconInfoStatement->bindText(2, snapshot.iconURL());
2058         m_updateIconInfoStatement->bindInt64(3, iconID);
2059 
2060         if (m_updateIconInfoStatement->step() != SQLResultDone)
2061             LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
2062 
2063         m_updateIconInfoStatement->reset();
2064 
2065         readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
2066         m_updateIconDataStatement->bindInt64(2, iconID);
2067 
2068         // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2069         // signifying that this icon doesn't have any data
2070         if (snapshot.data() && snapshot.data()->size())
2071             m_updateIconDataStatement->bindBlob(1, snapshot.data()->data(), snapshot.data()->size());
2072         else
2073             m_updateIconDataStatement->bindNull(1);
2074 
2075         if (m_updateIconDataStatement->step() != SQLResultDone)
2076             LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
2077 
2078         m_updateIconDataStatement->reset();
2079     } else {
2080         readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
2081         m_setIconInfoStatement->bindText(1, snapshot.iconURL());
2082         m_setIconInfoStatement->bindInt64(2, snapshot.timestamp());
2083 
2084         if (m_setIconInfoStatement->step() != SQLResultDone)
2085             LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
2086 
2087         m_setIconInfoStatement->reset();
2088 
2089         int64_t iconID = m_syncDB.lastInsertRowID();
2090 
2091         readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
2092         m_setIconDataStatement->bindInt64(1, iconID);
2093 
2094         // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2095         // signifying that this icon doesn't have any data
2096         if (snapshot.data() && snapshot.data()->size())
2097             m_setIconDataStatement->bindBlob(2, snapshot.data()->data(), snapshot.data()->size());
2098         else
2099             m_setIconDataStatement->bindNull(2);
2100 
2101         if (m_setIconDataStatement->step() != SQLResultDone)
2102             LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL()).ascii().data());
2103 
2104         m_setIconDataStatement->reset();
2105     }
2106 }
2107 
wasExcludedFromBackup()2108 bool IconDatabase::wasExcludedFromBackup()
2109 {
2110     ASSERT_ICON_SYNC_THREAD();
2111 
2112     return SQLiteStatement(m_syncDB, "SELECT value FROM IconDatabaseInfo WHERE key = 'ExcludedFromBackup';").getColumnInt(0);
2113 }
2114 
setWasExcludedFromBackup()2115 void IconDatabase::setWasExcludedFromBackup()
2116 {
2117     ASSERT_ICON_SYNC_THREAD();
2118 
2119     SQLiteStatement(m_syncDB, "INSERT INTO IconDatabaseInfo (key, value) VALUES ('ExcludedFromBackup', 1)").executeCommand();
2120 }
2121 
2122 class ClientWorkItem {
2123 public:
ClientWorkItem(IconDatabaseClient * client)2124     ClientWorkItem(IconDatabaseClient* client)
2125         : m_client(client)
2126     { }
2127     virtual void performWork() = 0;
~ClientWorkItem()2128     virtual ~ClientWorkItem() { }
2129 
2130 protected:
2131     IconDatabaseClient* m_client;
2132 };
2133 
2134 class ImportedIconURLForPageURLWorkItem : public ClientWorkItem {
2135 public:
ImportedIconURLForPageURLWorkItem(IconDatabaseClient * client,const String & pageURL)2136     ImportedIconURLForPageURLWorkItem(IconDatabaseClient* client, const String& pageURL)
2137         : ClientWorkItem(client)
2138         , m_pageURL(new String(pageURL.threadsafeCopy()))
2139     { }
2140 
~ImportedIconURLForPageURLWorkItem()2141     virtual ~ImportedIconURLForPageURLWorkItem()
2142     {
2143         delete m_pageURL;
2144     }
2145 
performWork()2146     virtual void performWork()
2147     {
2148         ASSERT(m_client);
2149         m_client->didImportIconURLForPageURL(*m_pageURL);
2150         m_client = 0;
2151     }
2152 
2153 private:
2154     String* m_pageURL;
2155 };
2156 
2157 class ImportedIconDataForPageURLWorkItem : public ClientWorkItem {
2158 public:
ImportedIconDataForPageURLWorkItem(IconDatabaseClient * client,const String & pageURL)2159     ImportedIconDataForPageURLWorkItem(IconDatabaseClient* client, const String& pageURL)
2160         : ClientWorkItem(client)
2161         , m_pageURL(new String(pageURL.threadsafeCopy()))
2162     { }
2163 
~ImportedIconDataForPageURLWorkItem()2164     virtual ~ImportedIconDataForPageURLWorkItem()
2165     {
2166         delete m_pageURL;
2167     }
2168 
performWork()2169     virtual void performWork()
2170     {
2171         ASSERT(m_client);
2172         m_client->didImportIconDataForPageURL(*m_pageURL);
2173         m_client = 0;
2174     }
2175 
2176 private:
2177     String* m_pageURL;
2178 };
2179 
2180 class RemovedAllIconsWorkItem : public ClientWorkItem {
2181 public:
RemovedAllIconsWorkItem(IconDatabaseClient * client)2182     RemovedAllIconsWorkItem(IconDatabaseClient* client)
2183         : ClientWorkItem(client)
2184     { }
2185 
performWork()2186     virtual void performWork()
2187     {
2188         ASSERT(m_client);
2189         m_client->didRemoveAllIcons();
2190         m_client = 0;
2191     }
2192 };
2193 
2194 class FinishedURLImport : public ClientWorkItem {
2195 public:
FinishedURLImport(IconDatabaseClient * client)2196     FinishedURLImport(IconDatabaseClient* client)
2197         : ClientWorkItem(client)
2198     { }
2199 
performWork()2200     virtual void performWork()
2201     {
2202         ASSERT(m_client);
2203         m_client->didFinishURLImport();
2204         m_client = 0;
2205     }
2206 };
2207 
performWorkItem(void * context)2208 static void performWorkItem(void* context)
2209 {
2210     ClientWorkItem* item = static_cast<ClientWorkItem*>(context);
2211     item->performWork();
2212     delete item;
2213 }
2214 
dispatchDidImportIconURLForPageURLOnMainThread(const String & pageURL)2215 void IconDatabase::dispatchDidImportIconURLForPageURLOnMainThread(const String& pageURL)
2216 {
2217     ASSERT_ICON_SYNC_THREAD();
2218 
2219     ImportedIconURLForPageURLWorkItem* work = new ImportedIconURLForPageURLWorkItem(m_client, pageURL);
2220     callOnMainThread(performWorkItem, work);
2221 }
2222 
dispatchDidImportIconDataForPageURLOnMainThread(const String & pageURL)2223 void IconDatabase::dispatchDidImportIconDataForPageURLOnMainThread(const String& pageURL)
2224 {
2225     ASSERT_ICON_SYNC_THREAD();
2226 
2227     ImportedIconDataForPageURLWorkItem* work = new ImportedIconDataForPageURLWorkItem(m_client, pageURL);
2228     callOnMainThread(performWorkItem, work);
2229 }
2230 
dispatchDidRemoveAllIconsOnMainThread()2231 void IconDatabase::dispatchDidRemoveAllIconsOnMainThread()
2232 {
2233     ASSERT_ICON_SYNC_THREAD();
2234 
2235     RemovedAllIconsWorkItem* work = new RemovedAllIconsWorkItem(m_client);
2236     callOnMainThread(performWorkItem, work);
2237 }
2238 
dispatchDidFinishURLImportOnMainThread()2239 void IconDatabase::dispatchDidFinishURLImportOnMainThread()
2240 {
2241     ASSERT_ICON_SYNC_THREAD();
2242 
2243     FinishedURLImport* work = new FinishedURLImport(m_client);
2244     callOnMainThread(performWorkItem, work);
2245 }
2246 
2247 
2248 } // namespace WebCore
2249 
2250 #endif // ENABLE(ICONDATABASE)
2251