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