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