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