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