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