3d5672bf6f5fc676403bc8cdf6d1a4e319250a71
[WebKit-https.git] / WebKit / Misc / WebIconDatabase.m
1 /*
2  * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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. 
16  *
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.
27  */
28 #import <WebKit/WebIconDatabase.h>
29
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>
36
37 #import <WebKit/WebIconDatabaseBridge.h>
38
39 #import "WebTypesInternal.h"
40
41 NSString * const WebIconDatabaseVersionKey =    @"WebIconDatabaseVersion";
42 NSString * const WebURLToIconURLKey =           @"WebSiteURLToIconURLKey";
43
44 NSString * const ObsoleteIconsOnDiskKey =       @"WebIconsOnDisk";
45 NSString * const ObsoleteIconURLToURLsKey =     @"WebIconURLToSiteURLs";
46
47 static const int WebIconDatabaseCurrentVersion = 2;
48
49 NSString *WebIconDatabaseDidAddIconNotification =          @"WebIconDatabaseDidAddIconNotification";
50 NSString *WebIconNotificationUserInfoURLKey =              @"WebIconNotificationUserInfoURLKey";
51 NSString *WebIconDatabaseDidRemoveAllIconsNotification =   @"WebIconDatabaseDidRemoveAllIconsNotification";
52
53 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
54 NSString *WebIconDatabaseEnabledDefaultsKey =   @"WebIconDatabaseEnabled";
55
56 NSString *WebIconDatabasePath = @"~/Library/Icons";
57
58 NSSize WebIconSmallSize = {16, 16};
59 NSSize WebIconMediumSize = {32, 32};
60 NSSize WebIconLargeSize = {128, 128};
61
62 @interface NSMutableDictionary (WebIconDatabase)
63 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key;
64 @end
65
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; 
86 @end
87
88 @implementation WebIconDatabase
89
90 + (WebIconDatabase *)sharedIconDatabase
91 {
92     static WebIconDatabase *database = nil;
93     
94     if (!database) {
95 #if !LOG_DISABLED
96         double start = CFAbsoluteTimeGetCurrent();
97 #endif
98         database = [[WebIconDatabase alloc] init];
99 #if !LOG_DISABLED
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));
102 #endif
103     }
104     return database;
105 }
106
107
108 - init
109 {
110     [super init];
111     
112     _private = [[WebIconDatabasePrivate alloc] init];
113
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]) {
119         return self;
120     }
121     
122     [self _createFileDatabase];
123     [self _loadIconDictionaries];
124
125     _isClosing = NO;
126
127 #ifdef ICONDEBUG
128     _private->databaseBridge = [WebIconDatabaseBridge sharedBridgeInstance];
129     if (_private->databaseBridge) {
130         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
131         NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
132
133         if (!databaseDirectory) {
134             databaseDirectory = WebIconDatabasePath;
135             [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
136         }
137         databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
138         [_private->databaseBridge openSharedDatabaseWithPath:databaseDirectory];
139     }
140     
141     [self _convertToWebCoreFormat];
142 #else
143     _private->databaseBridge = nil;
144 #endif
145     
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];
155     
156     [[NSNotificationCenter defaultCenter] addObserver:self
157                                              selector:@selector(_applicationWillTerminate:)
158                                                  name:NSApplicationWillTerminateNotification
159                                                object:NSApp];
160     [[NSNotificationCenter defaultCenter] 
161             addObserver:self selector:@selector(_resetCachedWebPreferences:) 
162                    name:WebPreferencesChangedNotification object:nil];
163                    
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
167 #ifndef ICONDEBUG
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];
172 #endif
173
174     return self;
175 }
176
177 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
178 {
179     ASSERT(size.width);
180     ASSERT(size.height);
181
182     if (!URL || ![self _isEnabled])
183         return [self defaultIconWithSize:size];
184
185     if ([URL _webkit_isFileURL])
186         return [self _iconForFileURL:URL withSize:size];
187
188 #ifdef ICONDEBUG        
189     NSImage* image = [_private->databaseBridge iconForPageURL:URL withSize:size];
190         
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];
193 #endif
194
195     NSString *iconURLString = [_private->pageURLToIconURL objectForKey:URL];
196     if (!iconURLString)
197         // Don't have it
198         return [self defaultIconWithSize:size];
199
200     NSMutableDictionary *icons = [self _iconsForIconURLString:iconURLString];
201
202     if (!icons) {
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];
208         }
209         return [self defaultIconWithSize:size];
210     }        
211
212     return [self _iconFromDictionary:icons forSize:size cache:cache];
213 }
214
215 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
216 {
217     return [self iconForURL:URL withSize:size cache:YES];
218 }
219
220 - (NSString *)iconURLForURL:(NSString *)URL
221 {
222     if (![self _isEnabled])
223         return nil;
224         
225 #ifdef ICONDEBUG
226     NSString* iconurl = [_private->databaseBridge iconURLForPageURL:URL];
227     return iconurl;
228 #endif
229
230     return URL ? [_private->pageURLToIconURL objectForKey:URL] : nil;
231 }
232
233 - (NSImage *)defaultIconWithSize:(NSSize)size
234 {
235     ASSERT(size.width);
236     ASSERT(size.height);
237     
238     if (!_private->defaultIcons) {
239         NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"url_icon" ofType:@"tiff"];
240         if (path) {
241             NSImage *icon = [[NSImage alloc] initByReferencingFile:path];
242             _private->defaultIcons = [[NSMutableDictionary dictionaryWithObject:icon
243                                             forKey:[NSValue valueWithSize:[icon size]]] retain];
244             [icon release];
245         }
246     }
247
248     return [self _iconFromDictionary:_private->defaultIcons forSize:size cache:YES];
249 }
250
251 - (void)retainIconForURL:(NSString *)URL
252 {
253     ASSERT(URL);
254     if (![self _isEnabled])
255         return;
256
257 #ifdef ICONDEBUG
258     [_private->databaseBridge retainIconForURL:URL];
259     return;
260 #endif
261
262     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URL);
263     CFDictionarySetValue(_private->pageURLToRetainCount, URL, (void *)(retainCount + 1));
264
265 }
266
267 - (void)releaseIconForURL:(NSString *)pageURL
268 {
269     ASSERT(pageURL);
270     if (![self _isEnabled])
271         return;
272         
273 #ifdef ICONDEBUG
274     [_private->databaseBridge releaseIconForURL:pageURL];
275     return;
276 #endif
277
278     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, pageURL);
279     
280     if (retainCount <= 0) {
281         LOG_ERROR("The icon for %@ was released more times than it was retained.", pageURL);
282         return;
283     }
284     
285     WebNSUInteger newRetainCount = retainCount - 1;
286
287     if (newRetainCount == 0) {
288         // Forget association between this page URL and a retain count
289         CFDictionaryRemoveValue(_private->pageURLToRetainCount, pageURL);
290
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];
297             } else {
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];
305                     
306                     // Maybe this was the last page URL mapped to this icon URL
307                     if ([(NSMutableSet *)pageURLs count] == 0) {
308                         [_private->iconURLToPageURLs removeObjectForKey:iconURL];
309                     }
310                 } else {
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];
315                 }
316                 
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];
320             }
321         }
322     } else {
323         CFDictionarySetValue(_private->pageURLToRetainCount, pageURL, (void *)newRetainCount);
324     }
325 }
326
327 - (void)delayDatabaseCleanup
328 {
329     if (![self _isEnabled]) {
330         return;
331     }
332     
333     if(_private->didCleanup){
334         LOG_ERROR("delayDatabaseCleanup cannot be called after cleanup has begun");
335         return;
336     }
337     
338     _private->cleanupCount++;
339 }
340
341 - (void)allowDatabaseCleanup
342 {
343     if (![self _isEnabled]) {
344         return;
345     }
346     
347     if(_private->didCleanup){
348         LOG_ERROR("allowDatabaseCleanup cannot be called after cleanup has begun");
349         return;
350     }
351     
352     _private->cleanupCount--;
353
354     if(_private->cleanupCount == 0 && _private->waitingToCleanup){
355         [self _releaseOriginalIconsOnDisk];
356     }
357 }
358
359 @end
360
361
362 @implementation WebIconDatabase (WebPendingPublic)
363
364 - (void)removeAllIcons
365 {
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]];
370     
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
384                                                         object:self
385                                                       userInfo:nil];
386 }
387 - (BOOL)isIconExpiredForIconURL:(NSString *)iconURL
388 {
389     return [_private->databaseBridge isIconExpiredForIconURL:iconURL];
390 }
391
392 @end
393
394 @implementation WebIconDatabase (WebPrivate)
395
396 - (BOOL)_isEnabled
397 {
398     return (_private->fileDatabase != nil);
399 }
400
401 - (void)_setIcon:(NSImage *)icon forIconURL:(NSString *)iconURL
402 {
403     ASSERT(icon);
404     ASSERT(iconURL);
405     ASSERT([self _isEnabled]);
406     
407     NSMutableDictionary *icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
408     
409     if (!icons)
410         return;
411     
412     [_private->iconURLToIcons setObject:icons forKey:iconURL];
413     
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];
418
419     [self _retainIconForIconURLString:iconURL];
420     
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];
425 }
426
427 - (void)_setHaveNoIconForIconURL:(NSString *)iconURL
428 {
429     ASSERT(iconURL);
430     ASSERT([self _isEnabled]);
431
432 #ifdef ICONDEBUG
433     [_private->databaseBridge _setHaveNoIconForIconURL:iconURL];
434     return;
435 #endif
436
437     [_private->iconURLsWithNoIcons addObject:iconURL];
438     
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];
443
444     [self _retainIconForIconURLString:iconURL];
445
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];
450 }
451
452
453 - (void)_setIconURL:(NSString *)iconURL forURL:(NSString *)URL
454 {
455     ASSERT(iconURL);
456     ASSERT(URL);
457     ASSERT([self _isEnabled]);
458     ASSERT(_private->pageURLToIconURL);
459     
460 #ifdef ICONDEBUG
461     [_private->databaseBridge _setIconURL:iconURL forPageURL:URL];
462     [self _sendNotificationForURL:URL];
463     return;
464 #endif
465
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
468         return;
469     }
470
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];
475
476     [_private->pageURLToIconURL setObject:iconURL forKey:URL];
477     [_private->iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
478
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.
483         
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.
487         return;
488     }
489     
490     [self _sendNotificationForURL:URL];
491     [self _updateFileDatabase];
492 }
493
494 - (BOOL)_hasEntryForIconURL:(NSString *)iconURL;
495 {
496     ASSERT([self _isEnabled]);
497
498 #ifdef ICONDEBUG
499     BOOL result = [_private->databaseBridge _hasEntryForIconURL:iconURL];
500     if (result)
501         LOG(IconDatabase, "NewDB has icon for IconURL %@", iconURL);
502     else
503         LOG(IconDatabase, "NewDB has NO icon for IconURL %@", iconURL);
504     return result;
505 #endif
506
507     return (([_private->iconURLToIcons objectForKey:iconURL] ||
508              [_private->iconURLsWithNoIcons containsObject:iconURL] ||
509              [_private->iconsOnDiskWithURLs containsObject:iconURL]) &&
510              [self _totalRetainCountForIconURLString:iconURL] > 0);
511 }
512
513 - (void)_sendNotificationForURL:(NSString *)URL
514 {
515     ASSERT(URL);
516     
517     NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
518                                                          forKey:WebIconNotificationUserInfoURLKey];
519     [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification
520                                                         object:self
521                                                       userInfo:userInfo];
522 }
523
524 - (void)loadIconFromURL:(NSString *)iconURL
525 {
526     [_private->databaseBridge loadIconFromURL:iconURL];
527 }
528
529 @end
530
531 @implementation WebIconDatabase (WebInternal)
532
533 - (void)_createFileDatabase
534 {
535     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
536     NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
537
538     if (!databaseDirectory) {
539         databaseDirectory = WebIconDatabasePath;
540         [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
541     }
542     databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
543     
544     _private->fileDatabase = [[WebFileDatabase alloc] initWithPath:databaseDirectory];
545     [_private->fileDatabase setSizeLimit:20000000];
546     [_private->fileDatabase open];
547 }
548
549 - (void)_clearDictionaries
550 {
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];
563 }
564
565 - (void)_loadIconDictionaries
566 {
567     WebFileDatabase *fileDB = _private->fileDatabase;
568     
569     // fileDB should be non-nil here because it should have been created by _createFileDatabase 
570     if (!fileDB) {
571         LOG_ERROR("Couldn't load icon dictionaries because file database didn't exist");
572         return;
573     }
574     
575     NSNumber *version = [fileDB objectForKey:WebIconDatabaseVersionKey];
576     int v = 0;
577     // no version means first version
578     if (version == nil) {
579         v = 1;
580     } else if ([version isKindOfClass:[NSNumber class]]) {
581         v = [version intValue];
582     }
583     
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];
592         }        
593     }
594     
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];
601         return;
602     }
603
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]];
606
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];
610     NSString *URL;
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];
617             return;
618         }
619         [iconURLToPageURLs _web_setObjectUsingSetIfNecessary:URL forKey:iconURL];
620     }
621
622     ASSERT(!_private->pageURLToIconURL);
623     ASSERT(!_private->iconURLToPageURLs);
624     ASSERT(!_private->iconsOnDiskWithURLs);
625     ASSERT(!_private->originalIconsOnDiskWithURLs);
626     
627     _private->pageURLToIconURL = [pageURLToIconURL retain];
628     _private->iconURLToPageURLs = [iconURLToPageURLs retain];
629     _private->iconsOnDiskWithURLs = [iconsOnDiskWithURLs retain];
630     _private->originalIconsOnDiskWithURLs = [iconsOnDiskWithURLs copy];
631 }
632
633 // Only called by _setIconURL:forKey:
634 - (void)_updateFileDatabase
635 {
636     if (_private->cleanupCount != 0)
637         return;
638
639     WebFileDatabase *fileDB = _private->fileDatabase;
640     if (!fileDB) {
641         LOG_ERROR("Couldn't update file database because it didn't exist");
642         return;
643     }
644
645     [fileDB setObject:[NSNumber numberWithInt:WebIconDatabaseCurrentVersion] forKey:WebIconDatabaseVersionKey];
646
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;
651     
652     while ((iconURLString = [enumerator nextObject]) != nil) {
653         [fileDB removeObjectForKey:iconURLString];
654         [_private->iconsOnDiskWithURLs removeObject:iconURLString];
655     }
656
657     // Save icons that have been retained that are not already on disk
658     enumerator = [_private->iconsToSaveWithURLs objectEnumerator];
659
660     while ((iconURLString = [enumerator nextObject]) != nil) {
661         NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
662         if (icons) {
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)]];
667             if (!icon) {
668                 // In case there is no 16 x 16 size.
669                 icon = [self _largestIconFromDictionary:icons];
670             }
671             NSData *iconData = [icon TIFFRepresentation];
672             if (iconData) {
673                 //NSLog(@"Writing icon: %@", iconURLString);
674                 [fileDB setObject:iconData forKey:iconURLString];
675                 [_private->iconsOnDiskWithURLs addObject:iconURLString];
676             }
677         } else if ([_private->iconURLsWithNoIcons containsObject:iconURLString]) {
678             [fileDB setObject:[NSNull null] forKey:iconURLString];
679             [_private->iconsOnDiskWithURLs addObject:iconURLString];
680         }
681     }
682     
683     [_private->iconsToEraseWithURLs removeAllObjects];
684     [_private->iconsToSaveWithURLs removeAllObjects];
685
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];
694 }
695
696 - (void)_applicationWillTerminate:(NSNotification *)notification
697 {
698     // Should only cause a write if user quit before 3 seconds after the last _updateFileDatabase
699     [_private->fileDatabase sync];
700     
701     [_private->databaseBridge closeSharedDatabase];
702     [_private->databaseBridge release];
703     _private->databaseBridge = nil;
704     _isClosing = YES;
705 }
706
707 - (int)_totalRetainCountForIconURLString:(NSString *)iconURLString
708 {
709     // Add up the retain counts for each associated page, plus the retain counts not associated
710     // with any page, which are stored in _private->iconURLToExtraRetainCount
711     WebNSUInteger result = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
712     
713     id URLStrings = [_private->iconURLToPageURLs objectForKey:iconURLString];
714     if (URLStrings != nil) {
715         if ([URLStrings isKindOfClass:[NSMutableSet class]]) {
716             NSEnumerator *e = [(NSMutableSet *)URLStrings objectEnumerator];
717             NSString *URLString;
718             while ((URLString = [e nextObject]) != nil) {
719                 ASSERT([URLString isKindOfClass:[NSString class]]);
720                 result += (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLString);
721             }
722         } else {
723             ASSERT([URLStrings isKindOfClass:[NSString class]]);
724             result += (WebNSUInteger)(void *)CFDictionaryGetValue(_private->pageURLToRetainCount, URLStrings);
725         }
726     }
727
728     return result;
729 }
730
731 - (NSMutableDictionary *)_iconsForIconURLString:(NSString *)iconURLString
732 {
733     ASSERT(iconURLString);
734
735     if ([_private->iconURLsWithNoIcons containsObject:iconURLString])
736         return nil;
737     
738     NSMutableDictionary *icons = [_private->iconURLToIcons objectForKey:iconURLString];
739
740     if (icons)
741         return icons;
742         
743     // Not in memory, check disk
744     if(![_private->iconsOnDiskWithURLs containsObject:iconURLString])
745         return nil;
746     
747 #if !LOG_DISABLED         
748     double start = CFAbsoluteTimeGetCurrent();
749 #endif
750     NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
751
752     if ([iconData isKindOfClass:[NSNull class]]) {
753         [_private->iconURLsWithNoIcons addObject:iconURLString];
754         return nil;
755     }
756     
757     if (iconData) {
758         NS_DURING
759             NSImage *icon = [[NSImage alloc] initWithData:iconData];
760             icons = [self _iconsBySplittingRepresentationsOfIcon:icon];
761             if (icons) {
762 #if !LOG_DISABLED 
763                 double duration = CFAbsoluteTimeGetCurrent() - start;
764                 LOG(Timing, "loading and creating icon %@ took %f seconds", iconURLString, duration);
765 #endif
766                 [_private->iconURLToIcons setObject:icons forKey:iconURLString];
767             }
768         NS_HANDLER
769             icons = nil;
770         NS_ENDHANDLER
771     }
772     
773     return icons;
774 }
775
776 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
777 {
778     ASSERT(size.width);
779     ASSERT(size.height);
780
781     NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
782     NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
783     NSString *suffix = [path pathExtension];
784     NSImage *icon = nil;
785     
786     if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
787         if (!_private->htmlIcons) {
788             icon = [workspace iconForFileType:@"html"];
789             _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
790         }
791         icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
792     } else {
793         if (!path || ![path isAbsolutePath]) {
794             // Return the generic icon when there is no path.
795             icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
796         } else {
797             icon = [workspace iconForFile:path];
798         }
799         [self _scaleIcon:icon toSize:size];
800     }
801
802     return icon;
803 }
804
805 - (void)_retainIconForIconURLString:(NSString *)iconURLString
806 {
807     ASSERT(iconURLString);
808     
809     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
810     WebNSUInteger newRetainCount = retainCount + 1;
811
812     CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
813
814     if (newRetainCount == 1 && !_private->privateBrowsingEnabled) {
815
816         // Either we know nothing about this icon and need to save it to disk, or we were planning to remove it
817         // from disk (as set up in _forgetIconForIconURLString:) and should stop that process.
818         if ([_private->iconsOnDiskWithURLs containsObject:iconURLString]) {
819             ASSERT(![_private->iconsToSaveWithURLs containsObject:iconURLString]);
820             [_private->iconsToEraseWithURLs removeObject:iconURLString];
821         } else {
822             ASSERT(![_private->iconsToEraseWithURLs containsObject:iconURLString]);
823             [_private->iconsToSaveWithURLs addObject:iconURLString];
824         }
825     }
826 }
827
828 - (void)_forgetIconForIconURLString:(NSString *)iconURLString
829 {
830     ASSERT_ARG(iconURLString, iconURLString != nil);
831     if([_private->iconsOnDiskWithURLs containsObject:iconURLString]){
832         [_private->iconsToEraseWithURLs addObject:iconURLString];
833         [_private->iconsToSaveWithURLs removeObject:iconURLString];
834     }
835         
836     // Remove the icon's images
837     [_private->iconURLToIcons removeObjectForKey:iconURLString];
838     
839     // Remove negative cache item for icon, if any
840     [_private->iconURLsWithNoIcons removeObject:iconURLString];
841     
842     // Remove the icon's associated site URLs, if any
843     [iconURLString retain];
844     id URLs = [_private->iconURLToPageURLs objectForKey:iconURLString];
845     if (URLs != nil) {
846         if ([URLs isKindOfClass:[NSMutableSet class]])
847             [_private->pageURLToIconURL removeObjectsForKeys:[URLs allObjects]];
848         else {
849             ASSERT([URLs isKindOfClass:[NSString class]]);
850             [_private->pageURLToIconURL removeObjectForKey:URLs];
851         }
852     }
853     [_private->iconURLToPageURLs removeObjectForKey:iconURLString];
854     CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
855     [iconURLString release];
856 }
857
858 - (void)_releaseIconForIconURLString:(NSString *)iconURLString
859 {
860     ASSERT(iconURLString);
861     
862     if (![self _isEnabled])
863         return;
864     
865     WebNSUInteger retainCount = (WebNSUInteger)(void *)CFDictionaryGetValue(_private->iconURLToExtraRetainCount, iconURLString);
866
867     // This error used to be an ASSERT() that was causing the build bot to fail.  The build bot was getting itself into a reproducible
868     // 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
869     // triggers a call to _forgetIconForIconURL which dumps everything about the icon - including the retain count.  A later call to releaseIconForURL
870     // would then ASSERT and crash the test as the retain count had be internally reset to zero
871     // 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
872     // handled gracefully in release builds.  Therefore we're changing it to a LOG_ERROR with the understanding that the sqlite icon database will not 
873     // have this issue due to its entirely different nature
874     if (retainCount <= 0) {
875         if (!_isClosing)
876             LOG_ERROR("Trying to release an icon whose retain-count is already non-positive");
877         return;
878     }
879     
880     WebNSUInteger newRetainCount = retainCount - 1;
881     if (newRetainCount == 0) {
882         CFDictionaryRemoveValue(_private->iconURLToExtraRetainCount, iconURLString);
883         if ([self _totalRetainCountForIconURLString:iconURLString] == 0) {
884             [self _forgetIconForIconURLString:iconURLString];
885         }
886     } else {
887         CFDictionarySetValue(_private->iconURLToExtraRetainCount, iconURLString, (void *)newRetainCount);
888     }
889 }
890
891 - (void)_retainOriginalIconsOnDisk
892 {
893     NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];;
894     NSString *iconURLString;
895     while ((iconURLString = [enumerator nextObject]) != nil) {
896         [self _retainIconForIconURLString:iconURLString];
897     }
898 }
899
900 - (void)_releaseOriginalIconsOnDisk
901 {
902     if (_private->cleanupCount > 0) {
903         _private->waitingToCleanup = YES;
904         return;
905     }
906
907     NSEnumerator *enumerator = [_private->originalIconsOnDiskWithURLs objectEnumerator];
908     NSString *iconURLString;
909     while ((iconURLString = [enumerator nextObject]) != nil) {
910         [self _releaseIconForIconURLString:iconURLString];
911     }
912     
913     [_private->originalIconsOnDiskWithURLs release];
914     _private->originalIconsOnDiskWithURLs = nil;
915
916     _private->didCleanup = YES;
917 }
918
919 - (void)_resetCachedWebPreferences:(NSNotification *)notification
920 {
921     BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
922
923 #ifdef ICONDEBUG
924     [_private->databaseBridge setPrivateBrowsingEnabled:privateBrowsingEnabledNow];
925     return;
926 #endif
927
928     if (privateBrowsingEnabledNow == _private->privateBrowsingEnabled)
929         return;
930     
931     _private->privateBrowsingEnabled = privateBrowsingEnabledNow;
932     
933     // When private browsing is turned off, forget everything we learned while it was on 
934     if (!_private->privateBrowsingEnabled) {
935         // Forget all of the icons whose existence we learned of during private browsing.
936         NSEnumerator *iconEnumerator = [_private->iconURLsBoundDuringPrivateBrowsing objectEnumerator];
937         NSString *iconURLString;
938         while ((iconURLString = [iconEnumerator nextObject]) != nil)
939             [self _forgetIconForIconURLString:iconURLString];
940
941         // Forget the relationships between page and icon that we learned during private browsing.
942         NSEnumerator *pageEnumerator = [_private->pageURLsBoundDuringPrivateBrowsing objectEnumerator];
943         NSString *pageURLString;
944         while ((pageURLString = [pageEnumerator nextObject]) != nil) {
945             [_private->pageURLToIconURL removeObjectForKey:pageURLString];
946             // Tell clients that these pages' icons have changed (to generic). The notification is named
947             // WebIconDatabaseDidAddIconNotification but it really means just "did change icon".
948             [self _sendNotificationForURL:pageURLString];
949         }
950         
951         [_private->iconURLsBoundDuringPrivateBrowsing removeAllObjects];
952         [_private->pageURLsBoundDuringPrivateBrowsing removeAllObjects];
953     }
954 }
955
956 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
957 {
958     ASSERT(icons);
959     
960     NSEnumerator *enumerator = [icons keyEnumerator];
961     NSValue *currentSize, *largestSize=nil;
962     float largestSizeArea=0;
963
964     while ((currentSize = [enumerator nextObject]) != nil) {
965         NSSize currentSizeSize = [currentSize sizeValue];
966         float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
967         if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
968             largestSize = currentSize;
969             largestSizeArea = currentSizeArea;
970         }
971     }
972
973     return [icons objectForKey:largestSize];
974 }
975
976 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
977 {
978     ASSERT(icon);
979
980     NSMutableDictionary *icons = [NSMutableDictionary dictionary];
981     NSEnumerator *enumerator = [[icon representations] objectEnumerator];
982     NSImageRep *rep;
983
984     while ((rep = [enumerator nextObject]) != nil) {
985         NSSize size = [rep size];
986         NSImage *subIcon = [[NSImage alloc] initWithSize:size];
987         [subIcon addRepresentation:rep];
988         [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
989         [subIcon release];
990     }
991
992     if([icons count] > 0)
993         return icons;
994
995     LOG_ERROR("icon has no representations");
996     
997     return nil;
998 }
999
1000 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
1001 {
1002     ASSERT(size.width);
1003     ASSERT(size.height);
1004
1005     NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
1006
1007     if(!icon){
1008         icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
1009         [self _scaleIcon:icon toSize:size];
1010
1011         if(cache){
1012             [icons setObject:icon forKey:[NSValue valueWithSize:size]];
1013         }
1014     }
1015
1016     return icon;
1017 }
1018
1019 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
1020 {
1021     ASSERT(size.width);
1022     ASSERT(size.height);
1023     
1024 #if !LOG_DISABLED        
1025     double start = CFAbsoluteTimeGetCurrent();
1026 #endif
1027     
1028     [icon setScalesWhenResized:YES];
1029     [icon setSize:size];
1030     
1031 #if !LOG_DISABLED
1032     double duration = CFAbsoluteTimeGetCurrent() - start;
1033     LOG(Timing, "scaling icon took %f seconds.", duration);
1034 #endif
1035 }
1036
1037 - (NSData *)_iconDataForIconURL:(NSString *)iconURLString
1038 {
1039     ASSERT(iconURLString);
1040     
1041     NSData *iconData = [_private->fileDatabase objectForKey:iconURLString];
1042     
1043     if ((id)iconData == (id)[NSNull null]) 
1044         return nil;
1045         
1046     return iconData;
1047 }
1048
1049 - (void)_convertToWebCoreFormat
1050 {
1051     ASSERT(_private);
1052     ASSERT(_private->databaseBridge);
1053     
1054     // If the WebCore Icon Database is not empty, we assume that this conversion has already
1055     // taken place and skip the rest of the steps 
1056     if (![_private->databaseBridge _isEmpty])
1057         return;
1058                 
1059     NSEnumerator *enumerator = [_private->pageURLToIconURL keyEnumerator];
1060     NSString *url, *iconURL;
1061     
1062     // First, we'll iterate through the PageURL->IconURL map
1063     while ((url = [enumerator nextObject]) != nil) {
1064         iconURL = [_private->pageURLToIconURL objectForKey:url];
1065         if (!iconURL)
1066             continue;
1067         [_private->databaseBridge _setIconURL:iconURL forPageURL:url];
1068     }    
1069     
1070     // Second, we'll iterate through the icon data we do have
1071     enumerator = [_private->iconsOnDiskWithURLs objectEnumerator];
1072     NSData *iconData;
1073     
1074     while ((url = [enumerator nextObject]) != nil) {
1075         iconData = [self _iconDataForIconURL:url];
1076         if (iconData)
1077             [_private->databaseBridge _setIconData:iconData forIconURL:url];
1078         else {
1079             // This really *shouldn't* happen, so it'd be good to track down why it might happen in a debug build
1080             // however, we do know how to handle it gracefully in release
1081             LOG_ERROR("%@ is marked as having an icon on disk, but we couldn't get the data for it", url);
1082             [_private->databaseBridge _setHaveNoIconForIconURL:url];
1083         }
1084     }
1085     
1086     // Finally, we'll iterate through the negative cache we have
1087     enumerator = [_private->iconURLsWithNoIcons objectEnumerator];
1088     while ((url = [enumerator nextObject]) != nil) 
1089         [_private->databaseBridge _setHaveNoIconForIconURL:url];
1090    
1091     // After we're done converting old style icons over to webcore icons, we delete the entire directory hierarchy 
1092     // for the old icon DB
1093     NSString *iconPath = [[NSUserDefaults standardUserDefaults] objectForKey:WebIconDatabaseDirectoryDefaultsKey];
1094     if (!iconPath)
1095         iconPath = WebIconDatabasePath;
1096     
1097     NSString *fullIconPath = [iconPath stringByExpandingTildeInPath];    
1098     NSFileManager *fileManager = [NSFileManager defaultManager];
1099     enumerator = [[fileManager directoryContentsAtPath:fullIconPath] objectEnumerator];
1100     
1101     NSString *databaseFilename = [_private->databaseBridge defaultDatabaseFilename];
1102
1103     NSString *file;
1104     while ((file = [enumerator nextObject]) != nil) {
1105         if ([file isEqualTo:databaseFilename])
1106             continue;
1107         NSString *filePath = [fullIconPath stringByAppendingPathComponent:file];
1108         if (![fileManager  removeFileAtPath:filePath handler:nil])
1109             LOG_ERROR("Failed to delete %@ from old icon directory", filePath);
1110     }
1111 }
1112
1113 @end
1114
1115 @implementation WebIconDatabasePrivate
1116
1117 @end
1118
1119 @implementation NSMutableDictionary (WebIconDatabase)
1120
1121 - (void)_web_setObjectUsingSetIfNecessary:(id)object forKey:(id)key
1122 {
1123     id previousObject = [self objectForKey:key];
1124     if (previousObject == nil) {
1125         [self setObject:object forKey:key];
1126     } else if ([previousObject isKindOfClass:[NSMutableSet class]]) {
1127         [previousObject addObject:object];
1128     } else {
1129         NSMutableSet *objects = [[NSMutableSet alloc] initWithObjects:previousObject, object, nil];
1130         [self setObject:objects forKey:key];
1131         [objects release];
1132     }
1133 }
1134
1135 @end