2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #import <WebKit/WebIconDatabase.h>
30 #import <WebKit/WebIconDatabasePrivate.h>
31 #import <WebKit/WebFileDatabase.h>
32 #import <WebKit/WebKitLogging.h>
33 #import <WebKit/WebKitNSStringExtras.h>
34 #import <WebKit/WebNSURLExtras.h>
35 #import <WebKit/WebPreferences.h>
37 #import <WebKit/WebIconDatabaseBridge.h>
39 #import "WebTypesInternal.h"
41 NSString * const WebIconDatabaseVersionKey = @"WebIconDatabaseVersion";
42 NSString * const WebURLToIconURLKey = @"WebSiteURLToIconURLKey";
44 NSString * const ObsoleteIconsOnDiskKey = @"WebIconsOnDisk";
45 NSString * const ObsoleteIconURLToURLsKey = @"WebIconURLToSiteURLs";
47 static const int WebIconDatabaseCurrentVersion = 2;
49 NSString *WebIconDatabaseDidAddIconNotification = @"WebIconDatabaseDidAddIconNotification";
50 NSString *WebIconNotificationUserInfoURLKey = @"WebIconNotificationUserInfoURLKey";
51 NSString *WebIconDatabaseDidRemoveAllIconsNotification = @"WebIconDatabaseDidRemoveAllIconsNotification";
53 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
54 NSString *WebIconDatabaseEnabledDefaultsKey = @"WebIconDatabaseEnabled";
56 NSString *WebIconDatabasePath = @"~/Library/Icons";
58 NSSize WebIconSmallSize = {16, 16};
59 NSSize WebIconMediumSize = {32, 32};
60 NSSize WebIconLargeSize = {128, 128};
62 @interface NSMutableDictionary (WebIconDatabase)
63 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key;
66 @interface WebIconDatabase (WebInternal)
67 - (void)_clearDictionaries;
68 - (void)_createFileDatabase;
69 - (void)_loadIconDictionaries;
70 - (void)_updateFileDatabase;
71 - (void)_forgetIconForIconURLString:(NSString *)iconURLString;
72 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURL;
73 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
74 - (void)_retainIconForIconURLString:(NSString *)iconURL;
75 - (void)_releaseIconForIconURLString:(NSString *)iconURL;
76 - (void)_retainOriginalIconsOnDisk;
77 - (void)_releaseOriginalIconsOnDisk;
78 - (void)_resetCachedWebPreferences:(NSNotification *)notification;
79 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString;
80 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
81 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
82 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
83 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
84 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString;
85 - (void)_convertToWebCoreFormat;
88 @implementation WebIconDatabase
90 + (WebIconDatabase *)sharedIconDatabase
92 static WebIconDatabase *database = nil;
96 double start = CFAbsoluteTimeGetCurrent();
98 database = [[WebIconDatabase alloc] init];
100 LOG(Timing, "initializing icon database with %d sites and %d icons took %f",
101 [database->_private->pageURLToIconURL count], [database->_private->iconURLToPageURLs count], (CFAbsoluteTimeGetCurrent() - start));
112 _private = [[WebIconDatabasePrivate alloc] init];
114 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
115 NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
116 [defaults registerDefaults:initialDefaults];
117 [initialDefaults release];
118 if (![defaults boolForKey:WebIconDatabaseEnabledDefaultsKey]) {
122 [self _createFileDatabase];
123 [self _loadIconDictionaries];
128 _private->databaseBridge = [WebIconDatabaseBridge sharedBridgeInstance];
129 if (_private->databaseBridge) {
130 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
131 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
133 if (!databaseDirectory) {
134 databaseDirectory = WebIconDatabasePath;
135 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
137 databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
138 [_private->databaseBridge openSharedDatabaseWithPath:databaseDirectory];
141 [self _convertToWebCoreFormat];
143 _private->databaseBridge = nil;
146 _private->iconURLToIcons = [[NSMutableDictionary alloc] init];
147 _private->iconURLToExtraRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
148 _private->pageURLToRetainCount = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
149 _private->iconsToEraseWithURLs = [[NSMutableSet alloc] init];
150 _private->iconsToSaveWithURLs = [[NSMutableSet alloc] init];
151 _private->iconURLsWithNoIcons = [[NSMutableSet alloc] init];
152 _private->iconURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
153 _private->pageURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
154 _private->privateBrowsingEnabled = [[WebPreferences standardPreferences] privateBrowsingEnabled];
156 [[NSNotificationCenter defaultCenter] addObserver:self
157 selector:@selector(_applicationWillTerminate:)
158 name:NSApplicationWillTerminateNotification
160 [[NSNotificationCenter defaultCenter]
161 addObserver:self selector:@selector(_resetCachedWebPreferences:)
162 name:WebPreferencesChangedNotification object:nil];
164 // FIXME - Once the new iconDB is the only game in town, we need to remove any of the WebFileDatabase code
165 // that is threaded and expects certain files to exist - certain files we rip right out from underneath it
166 // in the _convertToWebCoreFormat method
168 // Retain icons on disk then release them once clean-up has begun.
169 // This gives the client the opportunity to retain them before they are erased.
170 [self _retainOriginalIconsOnDisk];
171 [self performSelector:@selector(_releaseOriginalIconsOnDisk) withObject:nil afterDelay:0];
177 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
182 if (!URL || ![self _isEnabled])
183 return [self defaultIconWithSize:size];
185 if ([URL _webkit_isFileURL])
186 return [self _iconForFileURL:URL withSize:size];
189 NSImage* image = [_private->databaseBridge iconForPageURL:URL withSize:size];
191 // FIXME - We currently don't embed the default icon in the new WebCore IconDB, so we'll return the old version of it;
192 return image ? image : [self defaultIconWithSize:size];
195 NSString *iconURLString = [_private->pageURLToIconURL objectForKey:URL];
198 return [self defaultIconWithSize:size];
200 NSMutableDictionary *icons = [self _iconsForIconURLString:iconURLString];
203 if (![_private->iconURLsWithNoIcons containsObject:iconURLString]) {
204 // We used to have this icon, but don't have it anymore for some reason. (Bug? Deleted from
205 // disk behind our back?). Forget that we ever had it so it will be re-fetched next time.
206 LOG_ERROR("WebIconDatabase used to contain %@, but the icon file is missing. Now forgetting that we ever knew about this icon.", iconURLString);
207 [self _forgetIconForIconURLString:iconURLString];
209 return [self defaultIconWithSize:size];
212 return [self _iconFromDictionary:icons forSize:size cache:cache];
215 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
217 return [self iconForURL:URL withSize:size cache:YES];
220 - (NSString *)iconURLForURL:(NSString *)URL
222 if (![self _isEnabled])
226 NSString* iconurl = [_private->databaseBridge iconURLForPageURL:URL];
230 return URL ? [_private->pageURLToIconURL objectForKey:URL] : nil;
233 - (NSImage *)defaultIconWithSize:(NSSize)size
238 if (!_private->defaultIcons) {
239 NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"url_icon" ofType:@"tiff"];
241 NSImage *icon = [[NSImage alloc] initByReferencingFile:path];
242 _private->defaultIcons = [[NSMutableDictionary dictionaryWithObject:icon
243 forKey:[NSValue valueWithSize:[icon size]]] retain];
248 return [self _iconFromDictionary:_private->defaultIcons forSize:size cache:YES];
251 - (void)retainIconForURL:(NSString *)URL
254 if (![self _isEnabled])
258 [_private->databaseBridge retainIconForURL:URL];
262 WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URL);
263 CFDictionarySetValue(_private->pageURLToRetainCount, URL, (void *)(retainCount + 1));
267 - (void)releaseIconForURL:(NSString *)pageURL
270 if (![self _isEnabled])
274 [_private->databaseBridge releaseIconForURL:pageURL];
278 WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, pageURL);
280 if (retainCount <= 0) {
281 LOG_ERROR("The icon for %@ was released more times than it was retained.", pageURL);
285 WebNSUInteger newRetainCount = retainCount - 1;
287 if (newRetainCount == 0) {
288 // Forget association between this page URL and a retain count
289 CFDictionaryRemoveValue(_private->pageURLToRetainCount, pageURL);
291 // If there's a known iconURL for this page URL, we need to do more cleanup
292 NSString *iconURL = [_private->pageURLToIconURL objectForKey:pageURL];
293 if (iconURL != nil) {
294 // If there are no other retainers of this icon, forget it entirely
295 if ([self _totalRetainCountForIconURLString:iconURL] == 0) {
296 [self _forgetIconForIconURLString:iconURL];
298 // There's at least one other retainer of this icon, so we need to forget the
299 // two-way links between this page URL and the icon URL, without blowing away
300 // the icon entirely.
301 id pageURLs = [_private->iconURLToPageURLs objectForKey:iconURL];
302 if ([pageURLs isKindOfClass:[NSMutableSet class]]) {
303 ASSERT([pageURLs containsObject:pageURL]);
304 [pageURLs removeObject:pageURL];
306 // Maybe this was the last page URL mapped to this icon URL
307 if ([(NSMutableSet *)pageURLs count] == 0) {
308 [_private->iconURLToPageURLs removeObjectForKey:iconURL];
311 // Only one page URL was associated with this icon URL; it must have been us
312 ASSERT([pageURLs isKindOfClass:[NSString class]]);
313 ASSERT([pageURLs isEqualToString:pageURL]);
314 [_private->iconURLToPageURLs removeObjectForKey:pageURL];
317 // Remove iconURL from this dictionary last, since this might be the last
318 // reference and we need to use it as a key for _private->iconURLToPageURLs above.
319 [_private->pageURLToIconURL removeObjectForKey:pageURL];
323 CFDictionarySetValue(_private->pageURLToRetainCount, pageURL, (void *)newRetainCount);
327 - (void)delayDatabaseCleanup
329 if (![self _isEnabled]) {
333 if(_private->didCleanup){
334 LOG_ERROR("delayDatabaseCleanup cannot be called after cleanup has begun");
338 _private->cleanupCount++;
341 - (void)allowDatabaseCleanup
343 if (![self _isEnabled]) {
347 if(_private->didCleanup){
348 LOG_ERROR("allowDatabaseCleanup cannot be called after cleanup has begun");
352 _private->cleanupCount--;
354 if(_private->cleanupCount == 0 && _private->waitingToCleanup){
355 [self _releaseOriginalIconsOnDisk];
362 @implementation WebIconDatabase (WebPendingPublic)
364 - (void)removeAllIcons
366 NSArray *keys = [(NSDictionary *)_private->iconURLToPageURLs allKeys];
367 unsigned count = [keys count];
368 for (unsigned i = 0; i < count; i++)
369 [self _forgetIconForIconURLString:[keys objectAtIndex:i]];
371 // Delete entire file database immediately. This has at least three advantages over waiting for
372 // _updateFileDatabase to execute:
373 // (1) _updateFileDatabase won't execute until an icon has been added
374 // (2) this is faster
375 // (3) this deletes all the on-disk hierarchy (especially useful if due to past problems there are
376 // some stale files in that hierarchy)
377 [_private->fileDatabase removeAllObjects];
378 [_private->iconsToEraseWithURLs removeAllObjects];
379 [_private->iconsToSaveWithURLs removeAllObjects];
380 [_private->iconURLsBoundDuringPrivateBrowsing removeAllObjects];
381 [_private->pageURLsBoundDuringPrivateBrowsing removeAllObjects];
382 [self _clearDictionaries];
383 [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidRemoveAllIconsNotification
387 - (BOOL)isIconExpiredForIconURL:(NSString *)iconURL
389 return [_private->databaseBridge isIconExpiredForIconURL:iconURL];
394 @implementation WebIconDatabase (WebPrivate)
398 return (_private->fileDatabase != nil);
401 - (void)_setIcon:(NSImage *)icon forIconURL:(NSString *)iconURL
405 ASSERT([self _isEnabled]);
407 NSMutableDictionary *icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
412 [_private->iconURLToIcons setObject:icons forKey:iconURL];
414 // Don't update any icon information on disk during private browsing. Remember which icons have been
415 // affected during private browsing so we can forget this information when private browsing is turned off.
416 if (_private->privateBrowsingEnabled)
417 [_private->iconURLsBoundDuringPrivateBrowsing addObject:iconURL];
419 [self _retainIconForIconURLString:iconURL];
421 // Release the newly created icon much like an autorelease.
422 // This gives the client enough time to retain it.
423 // FIXME: Should use an actual autorelease here using a proxy object instead.
424 [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
427 - (void)_setHaveNoIconForIconURL:(NSString *)iconURL
430 ASSERT([self _isEnabled]);
433 [_private->databaseBridge _setHaveNoIconForIconURL:iconURL];
437 [_private->iconURLsWithNoIcons addObject:iconURL];
439 // Don't update any icon information on disk during private browsing. Remember which icons have been
440 // affected during private browsing so we can forget this information when private browsing is turned off.
441 if (_private->privateBrowsingEnabled)
442 [_private->iconURLsBoundDuringPrivateBrowsing addObject:iconURL];
444 [self _retainIconForIconURLString:iconURL];
446 // Release the newly created icon much like an autorelease.
447 // This gives the client enough time to retain it.
448 // FIXME: Should use an actual autorelease here using a proxy object instead.
449 [self performSelector:@selector(_releaseIconForIconURLString:) withObject:iconURL afterDelay:0];
453 - (void)_setIconURL:(NSString *)iconURL forURL:(NSString *)URL
457 ASSERT([self _isEnabled]);
458 ASSERT(_private->pageURLToIconURL);
461 [_private->databaseBridge _setIconURL:iconURL forPageURL:URL];
462 [self _sendNotificationForURL:URL];
466 if ([[_private->pageURLToIconURL objectForKey:URL] isEqualToString:iconURL]) {
467 // Don't do any work if the icon URL is already bound to the site URL
471 // Keep track of which entries in pageURLToIconURL were created during private browsing so that they can be skipped
472 // when saving to disk.
473 if (_private->privateBrowsingEnabled)
474 [_private->pageURLsBoundDuringPrivateBrowsing addObject:URL];
476 [_private->pageURLToIconURL setObject:iconURL forKey:URL];
477 [_private->iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
479 if ([_private->iconURLsWithNoIcons containsObject:iconURL]) {
480 // If the icon is in the negative cache (ie, there is no icon), avoid the
481 // work of delivering a notification for it or saving it to disk. This is a significant
482 // win on the iBench HTML test.
484 // This return must occur after the dictionary set calls above, so the icon record
485 // is properly retained. Otherwise, we'll forget that the site had no icon, and
486 // inefficiently request its icon again.
490 [self _sendNotificationForURL:URL];
491 [self _updateFileDatabase];
494 - (BOOL)_hasEntryForIconURL:(NSString *)iconURL;
496 ASSERT([self _isEnabled]);
499 BOOL result = [_private->databaseBridge _hasEntryForIconURL:iconURL];
501 LOG(IconDatabase, "NewDB has icon for IconURL %@", iconURL);
503 LOG(IconDatabase, "NewDB has NO icon for IconURL %@", iconURL);
507 return (([_private->iconURLToIcons objectForKey:iconURL] ||
508 [_private->iconURLsWithNoIcons containsObject:iconURL] ||
509 [_private->iconsOnDiskWithURLs containsObject:iconURL]) &&
510 [self _totalRetainCountForIconURLString:iconURL] > 0);
513 - (void)_sendNotificationForURL:(NSString *)URL
517 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
518 forKey:WebIconNotificationUserInfoURLKey];
519 [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification
524 - (void)loadIconFromURL:(NSString *)iconURL
526 [_private->databaseBridge loadIconFromURL:iconURL];
531 @implementation WebIconDatabase (WebInternal)
533 - (void)_createFileDatabase
535 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
536 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
538 if (!databaseDirectory) {
539 databaseDirectory = WebIconDatabasePath;
540 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
542 databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
544 _private->fileDatabase = [[WebFileDatabase alloc] initWithPath:databaseDirectory];
545 [_private->fileDatabase setSizeLimit:20000000];
546 [_private->fileDatabase open];
549 - (void)_clearDictionaries
551 [_private->pageURLToIconURL release];
552 [_private->iconURLToPageURLs release];
553 [_private->iconsOnDiskWithURLs release];
554 [_private->originalIconsOnDiskWithURLs release];
555 [_private->iconURLsBoundDuringPrivateBrowsing release];
556 [_private->pageURLsBoundDuringPrivateBrowsing release];
557 _private->pageURLToIconURL = [[NSMutableDictionary alloc] init];
558 _private->iconURLToPageURLs = [[NSMutableDictionary alloc] init];
559 _private->iconsOnDiskWithURLs = [[NSMutableSet alloc] init];
560 _private->originalIconsOnDiskWithURLs = [[NSMutableSet alloc] init];
561 _private->iconURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
562 _private->pageURLsBoundDuringPrivateBrowsing = [[NSMutableSet alloc] init];
565 - (void)_loadIconDictionaries
567 WebFileDatabase *fileDB = _private->fileDatabase;
569 // fileDB should be non-nil here because it should have been created by _createFileDatabase
571 LOG_ERROR("Couldn't load icon dictionaries because file database didn't exist");
575 NSNumber *version = [fileDB objectForKey:WebIconDatabaseVersionKey];
577 // no version means first version
578 if (version == nil) {
580 } else if ([version isKindOfClass:[NSNumber class]]) {
581 v = [version intValue];
584 // Get the site URL to icon URL dictionary from the file DB.
585 NSMutableDictionary *pageURLToIconURL = nil;
586 if (v <= WebIconDatabaseCurrentVersion) {
587 pageURLToIconURL = [fileDB objectForKey:WebURLToIconURLKey];
588 // Remove the old unnecessary mapping files.
589 if (v < WebIconDatabaseCurrentVersion) {
590 [fileDB removeObjectForKey:ObsoleteIconsOnDiskKey];
591 [fileDB removeObjectForKey:ObsoleteIconURLToURLsKey];
595 // Must double-check all values read from disk. If any are bogus, we just throw out the whole icon cache.
596 // We expect this to be nil if the icon cache has been cleared, so we shouldn't whine in that case.
597 if (![pageURLToIconURL isKindOfClass:[NSMutableDictionary class]]) {
598 if (pageURLToIconURL)
599 LOG_ERROR("Clearing icon cache because bad value %@ was found on disk, expected an NSMutableDictionary", pageURLToIconURL);
600 [self _clearDictionaries];
604 // Keep a set of icon URLs on disk so we know what we need to write out or remove.
605 NSMutableSet *iconsOnDiskWithURLs = [NSMutableSet setWithArray:[pageURLToIconURL allValues]];
607 // Reverse pageURLToIconURL so we have an icon URL to page URLs dictionary.
608 NSMutableDictionary *iconURLToPageURLs = [NSMutableDictionary dictionaryWithCapacity:[_private->iconsOnDiskWithURLs count]];
609 NSEnumerator *enumerator = [pageURLToIconURL keyEnumerator];
611 while ((URL = [enumerator nextObject])) {
612 NSString *iconURL = (NSString *)[pageURLToIconURL objectForKey:URL];
613 // Must double-check all values read from disk. If any are bogus, we just throw out the whole icon cache.
614 if (![URL isKindOfClass:[NSString class]] || ![iconURL isKindOfClass:[NSString class]]) {
615 LOG_ERROR("Clearing icon cache because either %@ or %@ was a bad value on disk, expected both to be NSStrings", URL, iconURL);
616 [self _clearDictionaries];
619 [iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
622 ASSERT(!_private->pageURLToIconURL);
623 ASSERT(!_private->iconURLToPageURLs);
624 ASSERT(!_private->iconsOnDiskWithURLs);
625 ASSERT(!_private->originalIconsOnDiskWithURLs);
627 _private->pageURLToIconURL = [pageURLToIconURL retain];
628 _private->iconURLToPageURLs = [iconURLToPageURLs retain];
629 _private->iconsOnDiskWithURLs = [iconsOnDiskWithURLs retain];
630 _private->originalIconsOnDiskWithURLs = [iconsOnDiskWithURLs copy];
633 // Only called by _setIconURL:forKey:
634 - (void)_updateFileDatabase
636 if (_private->cleanupCount != 0)
639 WebFileDatabase *fileDB = _private->fileDatabase;
641 LOG_ERROR("Couldn't update file database because it didn't exist");
645 [fileDB setObject:[NSNumber numberWithInt:WebIconDatabaseCurrentVersion] forKey:WebIconDatabaseVersionKey];
647 // Erase icons that have been released that are on disk.
648 // Must remove icons before writing them to disk or else we could potentially remove the newly written ones.
649 NSEnumerator *enumerator = [_private->iconsToEraseWithURLs objectEnumerator];
650 NSString *iconURLString;
652 while ((iconURLString = [enumerator nextObject]) != nil) {
653 [fileDB removeObjectForKey:iconURLString];
654 [_private->iconsOnDiskWithURLs removeObject:iconURLString];
657 // Save icons that have been retained that are not already on disk
658 enumerator = [_private->iconsToSaveWithURLs objectEnumerator];
660 while ((iconURLString = [enumerator nextObject]) != nil) {
661 NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
663 // Save the 16 x 16 size icons as this is the only size that Safari uses.
664 // If we ever use larger sizes, we should save the largest size so icons look better when scaling up.
665 // This also works around the problem with cnet's blank 32x32 icon (3105486).
666 NSImage *icon = [icons objectForKey:[NSValue valueWithSize:NSMakeSize(16,16)]];
668 // In case there is no 16 x 16 size.
669 icon = [self _largestIconFromDictionary:icons];
671 NSData *iconData = [icon TIFFRepresentation];
673 //NSLog(@"Writing icon: %@", iconURLString);
674 [fileDB setObject:iconData forKey:iconURLString];
675 [_private->iconsOnDiskWithURLs addObject:iconURLString];
677 } else if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
678 [fileDB setObject:[NSNull null] forKey:iconURLString];
679 [_private->iconsOnDiskWithURLs addObject:iconURLString];
683 [_private->iconsToEraseWithURLs removeAllObjects];
684 [_private->iconsToSaveWithURLs removeAllObjects];
686 // Save the icon dictionaries to disk, after removing any values created during private browsing.
687 // Even if we weren't modifying the dictionary we'd still want to use a copy so that WebFileDatabase
688 // doesn't look at the original from a different thread. (We used to think this would fix 3566336
689 // but that bug's progeny are still alive and kicking.)
690 NSMutableDictionary *pageURLToIconURLCopy = [_private->pageURLToIconURL mutableCopy];
691 [pageURLToIconURLCopy removeObjectsForKeys:[_private->pageURLsBoundDuringPrivateBrowsing allObjects]];
692 [fileDB setObject:pageURLToIconURLCopy forKey:WebURLToIconURLKey];
693 [pageURLToIconURLCopy release];
696 - (void)_applicationWillTerminate:(NSNotification *)notification
698 // Should only cause a write if user quit before 3 seconds after the last _updateFileDatabase
699 [_private->fileDatabase sync];
702 [_private->databaseBridge closeSharedDatabase];
703 [_private->databaseBridge release];
704 _private->databaseBridge = nil;
709 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString
711 // Add up the retain counts for each associated page, plus the retain counts not associated
712 // with any page, which are stored in _private->iconURLToExtraRetainCount
713 WebNSUInteger result = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
715 id URLStrings = [_private->iconURLToPageURLs objectForKey:iconURLString];
716 if (URLStrings != nil) {
717 if ([URLStrings isKindOfClass:[NSMutableSet class]]) {
718 NSEnumerator *e = [(NSMutableSet *)URLStrings objectEnumerator];
720 while ((URLString = [e nextObject]) != nil) {
721 ASSERT([URLString isKindOfClass:[NSString class]]);
722 result += (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLString);
725 ASSERT([URLStrings isKindOfClass:[NSString class]]);
726 result += (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLStrings);
733 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURLString
735 ASSERT(iconURLString);
737 if ([_private->iconURLsWithNoIcons containsObject:iconURLString])
740 NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
745 // Not in memory, check disk
746 if(![_private->iconsOnDiskWithURLs containsObject:iconURLString])
750 double start = CFAbsoluteTimeGetCurrent();
752 NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
754 if ([iconData isKindOfClass:[NSNull class]]) {
755 [_private->iconURLsWithNoIcons addObject:iconURLString];
761 NSImage *icon = [[NSImage alloc] initWithData:iconData];
762 icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
765 double duration = CFAbsoluteTimeGetCurrent() - start;
766 LOG(Timing, "loading and creating icon %@ took %f seconds", iconURLString, duration);
768 [_private->iconURLToIcons setObject:icons forKey:iconURLString];
778 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
783 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
784 NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
785 NSString *suffix = [path pathExtension];
788 if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
789 if (!_private->htmlIcons) {
790 icon = [workspace iconForFileType:@"html"];
791 _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
793 icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
795 if (!path || ![path isAbsolutePath]) {
796 // Return the generic icon when there is no path.
797 icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
799 icon = [workspace iconForFile:path];
801 [self _scaleIcon:icon toSize:size];
807 - (void)_retainIconForIconURLString:(NSString *)iconURLString
809 ASSERT(iconURLString);
811 WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
812 WebNSUInteger newRetainCount = retainCount + 1;
814 CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
816 if (newRetainCount == 1 && !_private->privateBrowsingEnabled) {
818 // Either we know nothing about this icon and need to save it to disk, or we were planning to remove it
819 // from disk (as set up in _forgetIconForIconURLString:) and should stop that process.
820 if ([_private->iconsOnDiskWithURLs containsObject:iconURLString]) {
821 ASSERT(![_private->iconsToSaveWithURLs containsObject:iconURLString]);
822 [_private->iconsToEraseWithURLs removeObject:iconURLString];
824 ASSERT(![_private->iconsToEraseWithURLs containsObject:iconURLString]);
825 [_private->iconsToSaveWithURLs addObject:iconURLString];
830 - (void)_forgetIconForIconURLString:(NSString *)iconURLString
832 ASSERT_ARG(iconURLString, iconURLString != nil);
833 if([_private->iconsOnDiskWithURLs containsObject:iconURLString]){
834 [_private->iconsToEraseWithURLs addObject:iconURLString];
835 [_private->iconsToSaveWithURLs removeObject:iconURLString];
838 // Remove the icon's images
839 [_private->iconURLToIcons removeObjectForKey:iconURLString];
841 // Remove negative cache item for icon, if any
842 [_private->iconURLsWithNoIcons removeObject:iconURLString];
844 // Remove the icon's associated site URLs, if any
845 [iconURLString retain];
846 id URLs = [_private->iconURLToPageURLs objectForKey:iconURLString];
848 if ([URLs isKindOfClass:[NSMutableSet class]])
849 [_private->pageURLToIconURL removeObjectsForKeys:[URLs allObjects]];
851 ASSERT([URLs isKindOfClass:[NSString class]]);
852 [_private->pageURLToIconURL removeObjectForKey:URLs];
855 [_private->iconURLToPageURLs removeObjectForKey:iconURLString];
856 CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
857 [iconURLString release];
860 - (void)_releaseIconForIconURLString:(NSString *)iconURLString
862 ASSERT(iconURLString);
864 if (![self _isEnabled])
867 WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
869 // This error used to be an ASSERT() that was causing the build bot to fail. The build bot was getting itself into a reproducible
870 // situation of having an icon for 127.0.0.1:8000/favicon.ico registered in the database but not finding the file for it. This situation
871 // triggers a call to _forgetIconForIconURL which dumps everything about the icon - including the retain count. A later call to releaseIconForURL
872 // would then ASSERT and crash the test as the retain count had be internally reset to zero
873 // The reason the build bot was getting into this situation is not yet understood but the cause of the ASSERT is - and the condition was already
874 // handled gracefully in release builds. Therefore we're changing it to a LOG_ERROR with the understanding that the sqlite icon database will not
875 // have this issue due to its entirely different nature
876 if (retainCount <= 0) {
878 LOG_ERROR("Trying to release an icon whose retain-count is already non-positive");
882 WebNSUInteger newRetainCount = retainCount - 1;
883 if (newRetainCount == 0) {
884 CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
885 if ([self _totalRetainCountForIconURLString:iconURLString] == 0) {
886 [self _forgetIconForIconURLString:iconURLString];
889 CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
893 - (void)_retainOriginalIconsOnDisk
895 NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];;
896 NSString *iconURLString;
897 while ((iconURLString = [enumerator nextObject]) != nil) {
898 [self _retainIconForIconURLString:iconURLString];
902 - (void)_releaseOriginalIconsOnDisk
904 if (_private->cleanupCount > 0) {
905 _private->waitingToCleanup = YES;
909 NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];
910 NSString *iconURLString;
911 while ((iconURLString = [enumerator nextObject]) != nil) {
912 [self _releaseIconForIconURLString:iconURLString];
915 [_private->originalIconsOnDiskWithURLs release];
916 _private->originalIconsOnDiskWithURLs = nil;
918 _private->didCleanup = YES;
921 - (void)_resetCachedWebPreferences:(NSNotification *)notification
923 BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
926 [_private->databaseBridge setPrivateBrowsingEnabled:privateBrowsingEnabledNow];
930 if (privateBrowsingEnabledNow == _private->privateBrowsingEnabled)
933 _private->privateBrowsingEnabled = privateBrowsingEnabledNow;
935 // When private browsing is turned off, forget everything we learned while it was on
936 if (!_private->privateBrowsingEnabled) {
937 // Forget all of the icons whose existence we learned of during private browsing.
938 NSEnumerator *iconEnumerator = [_private->iconURLsBoundDuringPrivateBrowsing objectEnumerator];
939 NSString *iconURLString;
940 while ((iconURLString = [iconEnumerator nextObject]) != nil)
941 [self _forgetIconForIconURLString:iconURLString];
943 // Forget the relationships between page and icon that we learned during private browsing.
944 NSEnumerator *pageEnumerator = [_private->pageURLsBoundDuringPrivateBrowsing objectEnumerator];
945 NSString *pageURLString;
946 while ((pageURLString = [pageEnumerator nextObject]) != nil) {
947 [_private->pageURLToIconURL removeObjectForKey:pageURLString];
948 // Tell clients that these pages' icons have changed (to generic). The notification is named
949 // WebIconDatabaseDidAddIconNotification but it really means just "did change icon".
950 [self _sendNotificationForURL:pageURLString];
953 [_private->iconURLsBoundDuringPrivateBrowsing removeAllObjects];
954 [_private->pageURLsBoundDuringPrivateBrowsing removeAllObjects];
958 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
962 NSEnumerator *enumerator = [icons keyEnumerator];
963 NSValue *currentSize, *largestSize=nil;
964 float largestSizeArea=0;
966 while ((currentSize = [enumerator nextObject]) != nil) {
967 NSSize currentSizeSize = [currentSize sizeValue];
968 float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
969 if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
970 largestSize = currentSize;
971 largestSizeArea = currentSizeArea;
975 return [icons objectForKey:largestSize];
978 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
982 NSMutableDictionary *icons = [NSMutableDictionary dictionary];
983 NSEnumerator *enumerator = [[icon representations] objectEnumerator];
986 while ((rep = [enumerator nextObject]) != nil) {
987 NSSize size = [rep size];
988 NSImage *subIcon = [[NSImage alloc] initWithSize:size];
989 [subIcon addRepresentation:rep];
990 [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
994 if([icons count] > 0)
997 LOG_ERROR("icon has no representations");
1002 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
1005 ASSERT(size.height);
1007 NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
1010 icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
1011 [self _scaleIcon:icon toSize:size];
1014 [icons setObject:icon forKey:[NSValue valueWithSize:size]];
1021 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
1024 ASSERT(size.height);
1027 double start = CFAbsoluteTimeGetCurrent();
1030 [icon setScalesWhenResized:YES];
1031 [icon setSize:size];
1034 double duration = CFAbsoluteTimeGetCurrent() - start;
1035 LOG(Timing, "scaling icon took %f seconds.", duration);
1039 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString
1041 ASSERT(iconURLString);
1043 NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
1045 if ((id)iconData == (id)[NSNull null])
1051 - (void)_convertToWebCoreFormat
1054 ASSERT(_private->databaseBridge);
1056 // If the WebCore Icon Database is not empty, we assume that this conversion has already
1057 // taken place and skip the rest of the steps
1058 if (![_private->databaseBridge _isEmpty])
1061 NSEnumerator *enumerator = [_private->pageURLToIconURL keyEnumerator];
1062 NSString *url, *iconURL;
1064 // First, we'll iterate through the PageURL->IconURL map
1065 while ((url = [enumerator nextObject]) != nil) {
1066 iconURL = [_private->pageURLToIconURL objectForKey:url];
1069 [_private->databaseBridge _setIconURL:iconURL forPageURL:url];
1072 // Second, we'll iterate through the icon data we do have
1073 enumerator = [_private->iconsOnDiskWithURLs objectEnumerator];
1076 while ((url = [enumerator nextObject]) != nil) {
1077 iconData = [self _iconDataForIconURL:url];
1079 [_private->databaseBridge _setIconData:iconData forIconURL:url];
1081 // This really *shouldn't* happen, so it'd be good to track down why it might happen in a debug build
1082 // however, we do know how to handle it gracefully in release
1083 LOG_ERROR("%@ is marked as having an icon on disk, but we couldn't get the data for it", url);
1084 [_private->databaseBridge _setHaveNoIconForIconURL:url];
1088 // Finally, we'll iterate through the negative cache we have
1089 enumerator = [_private->iconURLsWithNoIcons objectEnumerator];
1090 while ((url = [enumerator nextObject]) != nil)
1091 [_private->databaseBridge _setHaveNoIconForIconURL:url];
1093 // After we're done converting old style icons over to webcore icons, we delete the entire directory hierarchy
1094 // for the old icon DB
1095 NSString *iconPath = [[NSUserDefaults standardUserDefaults] objectForKey:WebIconDatabaseDirectoryDefaultsKey];
1097 iconPath = WebIconDatabasePath;
1099 NSString *fullIconPath = [iconPath stringByExpandingTildeInPath];
1100 NSFileManager *fileManager = [NSFileManager defaultManager];
1101 enumerator = [[fileManager directoryContentsAtPath:fullIconPath] objectEnumerator];
1103 NSString *databaseFilename = [_private->databaseBridge defaultDatabaseFilename];
1106 while ((file = [enumerator nextObject]) != nil) {
1107 if ([file isEqualTo:databaseFilename])
1109 NSString *filePath = [fullIconPath stringByAppendingPathComponent:file];
1110 if (![fileManager removeFileAtPath:filePath handler:nil])
1111 LOG_ERROR("Failed to delete %@ from old icon directory", filePath);
1117 @implementation WebIconDatabasePrivate
1121 @implementation NSMutableDictionary (WebIconDatabase)
1123 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key
1125 id previousObject = [self objectForKey:key];
1126 if (previousObject == nil) {
1127 [self setObject:object forKey:key];
1128 } else if ([previousObject isKindOfClass:[NSMutableSet class]]) {
1129 [previousObject addObject:object];
1131 NSMutableSet *objects = [[NSMutableSet alloc] initWithObjects:previousObject, object, nil];
1132 [self setObject:objects forKey:key];