1/*
2 * Copyright (C) 2006-2017 Apple Inc. All rights reserved.
3 * Copyright (C) 2007 Justin Haygood ([email protected])
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/Image.h>
33#include <WebCore/SQLiteStatement.h>
34#include <WebCore/SQLiteTransaction.h>
35#include <WebCore/SharedBuffer.h>
36#include <wtf/FileSystem.h>
37#include <wtf/MainThread.h>
38#include <wtf/NeverDestroyed.h>
39#include <wtf/StdLibExtras.h>
40#include <wtf/URL.h>
41#include <wtf/text/StringConcatenateNumbers.h>
42
43// For methods that are meant to support API from the main thread - should not be called internally
44#define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
45
46// For methods that are meant to support the sync thread ONLY
47#define IS_ICON_SYNC_THREAD() (m_syncThread == &Thread::current())
48#define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
49
50namespace WebKit {
51using namespace WebCore;
52
53static 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"
59static const int currentDatabaseVersion = 6;
60
61// Icons expire once every 4 days
62static const int iconExpirationTime = 60*60*24*4;
63
64static const Seconds updateTimerDelay { 5_s };
65
66static 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.
70static const Seconds notUsedIconExpirationTime { 60*60*24*30 };
71
72#if !LOG_DISABLED || !ERROR_DISABLED
73static 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
83class DefaultIconDatabaseClient final : public IconDatabaseClient {
84 WTF_MAKE_FAST_ALLOCATED;
85public:
86 void didImportIconURLForPageURL(const String&) override { }
87 void didImportIconDataForPageURL(const String&) override { }
88 void didChangeIconForPageURL(const String&) override { }
89 void didFinishURLImport() override { }
90};
91
92static IconDatabaseClient* defaultClient()
93{
94 static IconDatabaseClient* defaultClient = new DefaultIconDatabaseClient;
95 return defaultClient;
96}
97
98IconDatabase::IconRecord::IconRecord(const String& url)
99 : m_iconURL(url)
100{
101}
102
103IconDatabase::IconRecord::~IconRecord()
104{
105 LOG(IconDatabase, "Destroying IconRecord for icon url %s", m_iconURL.ascii().data());
106}
107
108NativeImagePtr 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
116void 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
141IconDatabase::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
150IconDatabase::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
158IconDatabase::PageURLRecord::PageURLRecord(const String& pageURL)
159 : m_pageURL(pageURL)
160{
161}
162
163IconDatabase::PageURLRecord::~PageURLRecord()
164{
165 if (m_iconRecord)
166 m_iconRecord->retainingPageURLs().remove(m_pageURL);
167}
168
169void 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
180IconDatabase::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
189void 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
200bool 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
230void 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
256void 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
297static bool documentCanHaveIcon(const String& documentURL)
298{
299 return !documentURL.isEmpty() && !protocolIs(documentURL, "about");
300}
301
302std::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
374String 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
400void 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
416void 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
451void 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
468void 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
525void 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
578void 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
646IconDatabase::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
674bool 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
685void IconDatabase::setEnabled(bool enabled)
686{
687 ASSERT_NOT_SYNC_THREAD();
688
689 if (!enabled && isOpen())
690 close();
691 m_isEnabled = enabled;
692}
693
694bool IconDatabase::isEnabled() const
695{
696 ASSERT_NOT_SYNC_THREAD();
697
698 return m_isEnabled;
699}
700
701void IconDatabase::setPrivateBrowsingEnabled(bool flag)
702{
703 m_privateBrowsingEnabled = flag;
704}
705
706bool IconDatabase::isPrivateBrowsingEnabled() const
707{
708 return m_privateBrowsingEnabled;
709}
710
711void IconDatabase::delayDatabaseCleanup()
712{
713 ++databaseCleanupCounter;
714 if (databaseCleanupCounter == 1)
715 LOG(IconDatabase, "Database cleanup is now DISABLED");
716}
717
718void IconDatabase::allowDatabaseCleanup()
719{
720 if (--databaseCleanupCounter < 0)
721 databaseCleanupCounter = 0;
722 if (!databaseCleanupCounter)
723 LOG(IconDatabase, "Database cleanup is now ENABLED");
724}
725
726void IconDatabase::checkIntegrityBeforeOpening()
727{
728 checkIntegrityOnOpen = true;
729}
730
731IconDatabase::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
739IconDatabase::~IconDatabase()
740{
741 ASSERT(!isOpen());
742}
743
744void IconDatabase::wakeSyncThread()
745{
746 LockHolder locker(m_syncLock);
747
748 m_syncThreadHasWorkToDo = true;
749 m_syncCondition.notifyOne();
750}
751
752void 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
766void IconDatabase::syncTimerFired()
767{
768 ASSERT_NOT_SYNC_THREAD();
769 wakeSyncThread();
770}
771
772// ******************
773// *** Any Thread ***
774// ******************
775
776bool IconDatabase::isOpen() const
777{
778 LockHolder locker(m_syncLock);
779 return m_syncThreadRunning || m_syncDB.isOpen();
780}
781
782String IconDatabase::databasePath() const
783{
784 LockHolder locker(m_syncLock);
785 return m_completeDatabasePath.isolatedCopy();
786}
787
788String 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"
795Ref<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
809IconDatabase::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
845bool IconDatabase::shouldStopThreadActivity() const
846{
847 ASSERT_ICON_SYNC_THREAD();
848
849 return m_threadTerminationRequested || m_removeIconsRequested;
850}
851
852void 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
934static int databaseVersionNumber(SQLiteDatabase& db)
935{
936 return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
937}
938
939static 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
953static 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
997void 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
1046bool 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
1079void 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 = makeString("SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID WHERE IconInfo.stamp > ", 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
1218void 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
1320void 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
1352bool 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
1452bool 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
1515void 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
1592void 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
1613void 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
1629void 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
1649void* 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
1686inline 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
1700void 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
1718void 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
1735void 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
1749int64_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
1769int64_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
1801RefPtr<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
1823void 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
1862void 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
1938void 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
1948void 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
1958void 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
1969