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