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