2 * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple 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.
29 #import "WebIconDatabaseInternal.h"
31 #import "WebIconDatabaseClient.h"
32 #import "WebIconDatabaseDelegate.h"
33 #import "WebKitLogging.h"
34 #import "WebKitNSStringExtras.h"
35 #import "WebNSFileManagerExtras.h"
36 #import "WebNSNotificationCenterExtras.h"
37 #import "WebNSURLExtras.h"
38 #import "WebPreferences.h"
39 #import "WebTypesInternal.h"
40 #import <WebCore/IconDatabase.h>
41 #import <WebCore/Image.h>
42 #import <WebCore/IntSize.h>
43 #import <WebCore/SharedBuffer.h>
44 #import <WebCore/ThreadCheck.h>
45 #import <runtime/InitializeThreading.h>
46 #import <wtf/Threading.h>
48 using namespace WebCore;
50 NSString * const WebIconDatabaseVersionKey = @"WebIconDatabaseVersion";
51 NSString * const WebURLToIconURLKey = @"WebSiteURLToIconURLKey";
53 NSString *WebIconDatabaseDidAddIconNotification = @"WebIconDatabaseDidAddIconNotification";
54 NSString *WebIconNotificationUserInfoURLKey = @"WebIconNotificationUserInfoURLKey";
55 NSString *WebIconDatabaseDidRemoveAllIconsNotification = @"WebIconDatabaseDidRemoveAllIconsNotification";
57 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
58 NSString *WebIconDatabaseImportDirectoryDefaultsKey = @"WebIconDatabaseImportDirectoryDefaultsKey";
59 NSString *WebIconDatabaseEnabledDefaultsKey = @"WebIconDatabaseEnabled";
61 NSString *WebIconDatabasePath = @"~/Library/Icons";
63 NSSize WebIconSmallSize = {16, 16};
64 NSSize WebIconMediumSize = {32, 32};
65 NSSize WebIconLargeSize = {128, 128};
67 #define UniqueFilePathSize (34)
69 static WebIconDatabaseClient* defaultClient()
71 #if ENABLE(ICONDATABASE)
72 static WebIconDatabaseClient* defaultClient = new WebIconDatabaseClient();
79 @interface WebIconDatabase (WebReallyInternal)
80 - (void)_sendNotificationForURL:(NSString *)URL;
81 - (void)_sendDidRemoveAllIconsNotification;
82 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
83 - (void)_resetCachedWebPreferences:(NSNotification *)notification;
84 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
85 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
86 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
87 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
88 - (NSString *)_databaseDirectory;
91 @implementation WebIconDatabase
95 JSC::initializeThreading();
96 WTF::initializeMainThreadToProcessMainThread();
99 + (WebIconDatabase *)sharedIconDatabase
101 static WebIconDatabase *database = nil;
103 database = [[WebIconDatabase alloc] init];
110 WebCoreThreadViolationCheckRoundOne();
112 _private = [[WebIconDatabasePrivate alloc] init];
114 // Check the user defaults and see if the icon database should even be enabled.
115 // Inform the bridge and, if we're disabled, bail from init right here
116 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
117 // <rdar://problem/4741419> - IconDatabase should be disabled by default
118 NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
119 [defaults registerDefaults:initialDefaults];
120 [initialDefaults release];
121 BOOL enabled = [defaults boolForKey:WebIconDatabaseEnabledDefaultsKey];
122 iconDatabase().setEnabled(enabled);
124 [self _startUpIconDatabase];
128 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
130 ASSERT_MAIN_THREAD();
134 if (!URL || ![self isEnabled])
135 return [self defaultIconForURL:URL withSize:size];
137 // FIXME - <rdar://problem/4697934> - Move the handling of FileURLs to WebCore and implement in ObjC++
138 if ([URL _webkit_isFileURL])
139 return [self _iconForFileURL:URL withSize:size];
141 if (Image* image = iconDatabase().iconForPageURL(URL, IntSize(size)))
142 if (NSImage *icon = webGetNSImage(image, size))
144 return [self defaultIconForURL:URL withSize:size];
147 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
149 return [self iconForURL:URL withSize:size cache:YES];
152 - (NSString *)iconURLForURL:(NSString *)URL
154 if (![self isEnabled])
156 ASSERT_MAIN_THREAD();
158 return iconDatabase().iconURLForPageURL(URL);
161 - (NSImage *)defaultIconWithSize:(NSSize)size
163 ASSERT_MAIN_THREAD();
167 Image* image = iconDatabase().defaultIcon(IntSize(size));
168 return image ? image->getNSImage() : nil;
171 - (NSImage *)defaultIconForURL:(NSString *)URL withSize:(NSSize)size
173 if (_private->delegateImplementsDefaultIconForURL)
174 return [_private->delegate webIconDatabase:self defaultIconForURL:URL withSize:size];
175 return [self defaultIconWithSize:size];
178 - (void)retainIconForURL:(NSString *)URL
180 ASSERT_MAIN_THREAD();
182 if (![self isEnabled])
185 iconDatabase().retainIconForPageURL(URL);
188 - (void)releaseIconForURL:(NSString *)pageURL
190 ASSERT_MAIN_THREAD();
192 if (![self isEnabled])
195 iconDatabase().releaseIconForPageURL(pageURL);
198 + (void)delayDatabaseCleanup
200 ASSERT_MAIN_THREAD();
202 IconDatabase::delayDatabaseCleanup();
205 + (void)allowDatabaseCleanup
207 ASSERT_MAIN_THREAD();
209 IconDatabase::allowDatabaseCleanup();
212 - (void)setDelegate:(id)delegate
214 _private->delegate = delegate;
215 _private->delegateImplementsDefaultIconForURL = [delegate respondsToSelector:@selector(webIconDatabase:defaultIconForURL:withSize:)];
220 return _private->delegate;
226 @implementation WebIconDatabase (WebPendingPublic)
230 return iconDatabase().isEnabled();
233 - (void)setEnabled:(BOOL)flag
235 BOOL currentlyEnabled = [self isEnabled];
236 if (currentlyEnabled && !flag) {
237 iconDatabase().setEnabled(false);
238 [self _shutDownIconDatabase];
239 } else if (!currentlyEnabled && flag) {
240 iconDatabase().setEnabled(true);
241 [self _startUpIconDatabase];
245 - (void)removeAllIcons
247 ASSERT_MAIN_THREAD();
248 if (![self isEnabled])
251 // Via the IconDatabaseClient interface, removeAllIcons() will send the WebIconDatabaseDidRemoveAllIconsNotification
252 iconDatabase().removeAllIcons();
257 @implementation WebIconDatabase (WebPrivate)
259 + (void)_checkIntegrityBeforeOpening
261 iconDatabase().checkIntegrityBeforeOpening();
266 @implementation WebIconDatabase (WebInternal)
268 - (void)_sendNotificationForURL:(NSString *)URL
272 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
273 forKey:WebIconNotificationUserInfoURLKey];
275 [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:WebIconDatabaseDidAddIconNotification
280 - (void)_sendDidRemoveAllIconsNotification
282 [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:WebIconDatabaseDidRemoveAllIconsNotification
287 - (void)_startUpIconDatabase
289 iconDatabase().setClient(defaultClient());
291 // Figure out the directory we should be using for the icon.db
292 NSString *databaseDirectory = [self _databaseDirectory];
294 // Rename legacy icon database files to the new icon database name
295 BOOL isDirectory = NO;
296 NSString *legacyDB = [databaseDirectory stringByAppendingPathComponent:@"icon.db"];
297 NSFileManager *defaultManager = [NSFileManager defaultManager];
298 if ([defaultManager fileExistsAtPath:legacyDB isDirectory:&isDirectory] && !isDirectory) {
299 NSString *newDB = [databaseDirectory stringByAppendingPathComponent:iconDatabase().defaultDatabaseFilename()];
300 if (![defaultManager fileExistsAtPath:newDB])
301 rename([legacyDB fileSystemRepresentation], [newDB fileSystemRepresentation]);
304 // Set the private browsing pref then open the WebCore icon database
305 iconDatabase().setPrivateBrowsingEnabled([[WebPreferences standardPreferences] privateBrowsingEnabled]);
306 if (!iconDatabase().open(databaseDirectory))
307 LOG_ERROR("Unable to open icon database");
309 // Register for important notifications
310 [[NSNotificationCenter defaultCenter] addObserver:self
311 selector:@selector(_applicationWillTerminate:)
312 name:NSApplicationWillTerminateNotification
314 [[NSNotificationCenter defaultCenter] addObserver:self
315 selector:@selector(_resetCachedWebPreferences:)
316 name:WebPreferencesChangedNotification
320 - (void)_shutDownIconDatabase
322 // Unregister for important notifications
323 [[NSNotificationCenter defaultCenter] removeObserver:self
324 name:NSApplicationWillTerminateNotification
326 [[NSNotificationCenter defaultCenter] removeObserver:self
327 name:WebPreferencesChangedNotification
331 - (void)_applicationWillTerminate:(NSNotification *)notification
333 iconDatabase().close();
336 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
338 ASSERT_MAIN_THREAD();
342 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
343 NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
344 NSString *suffix = [path pathExtension];
347 if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
348 if (!_private->htmlIcons) {
349 icon = [workspace iconForFileType:@"html"];
350 _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
352 icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
354 if (!path || ![path isAbsolutePath]) {
355 // Return the generic icon when there is no path.
356 icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
358 icon = [workspace iconForFile:path];
360 [self _scaleIcon:icon toSize:size];
366 - (void)_resetCachedWebPreferences:(NSNotification *)notification
368 BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
369 iconDatabase().setPrivateBrowsingEnabled(privateBrowsingEnabledNow);
372 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
376 NSEnumerator *enumerator = [icons keyEnumerator];
377 NSValue *currentSize, *largestSize=nil;
378 float largestSizeArea=0;
380 while ((currentSize = [enumerator nextObject]) != nil) {
381 NSSize currentSizeSize = [currentSize sizeValue];
382 float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
383 if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
384 largestSize = currentSize;
385 largestSizeArea = currentSizeArea;
389 return [icons objectForKey:largestSize];
392 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
396 NSMutableDictionary *icons = [NSMutableDictionary dictionary];
397 NSEnumerator *enumerator = [[icon representations] objectEnumerator];
400 while ((rep = [enumerator nextObject]) != nil) {
401 NSSize size = [rep size];
402 NSImage *subIcon = [[NSImage alloc] initWithSize:size];
403 [subIcon addRepresentation:rep];
404 [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
408 if([icons count] > 0)
411 LOG_ERROR("icon has no representations");
416 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
421 NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
424 icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
425 [self _scaleIcon:icon toSize:size];
428 [icons setObject:icon forKey:[NSValue valueWithSize:size]];
435 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
441 double start = CFAbsoluteTimeGetCurrent();
444 [icon setScalesWhenResized:YES];
448 double duration = CFAbsoluteTimeGetCurrent() - start;
449 LOG(Timing, "scaling icon took %f seconds.", duration);
453 // This hashing String->filename algorithm came from WebFileDatabase.m and is what was used in the
454 // WebKit Icon Database
455 static void legacyIconDatabaseFilePathForKey(id key, char *buffer)
463 s = [[[[key description] lowercaseString] stringByStandardizingPath] UTF8String];
466 // compute first hash
468 for (cnt = 0; cnt < len; cnt++) {
469 hash1 += (hash1 << 8) + s[cnt];
471 hash1 += (hash1 << (len & 31));
473 // compute second hash
475 for (cnt = 0; cnt < len; cnt++) {
476 hash2 = (37 * hash2) ^ s[cnt];
480 snprintf(buffer, UniqueFilePathSize, "%.2u/%.2u/%.10u-%.10u.cache", ((hash1 & 0xff) >> 4), ((hash2 & 0xff) >> 4), hash1, hash2);
482 snprintf(buffer, UniqueFilePathSize, "%.2lu/%.2lu/%.10lu-%.10lu.cache", ((hash1 & 0xff) >> 4), ((hash2 & 0xff) >> 4), hash1, hash2);
486 // This method of getting an object from the filesystem is taken from the old
487 // WebKit Icon Database
488 static id objectFromPathForKey(NSString *databasePath, id key)
493 // Use the key->filename hashing the old WebKit IconDatabase used
494 char uniqueKey[UniqueFilePathSize];
495 legacyIconDatabaseFilePathForKey(key, uniqueKey);
497 // Get the data from this file and setup for the un-archiving
498 NSString *filePath = [[NSString alloc] initWithFormat:@"%@/%s", databasePath, uniqueKey];
499 NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
500 NSUnarchiver *unarchiver = nil;
504 unarchiver = [[NSUnarchiver alloc] initForReadingWithData:data];
506 id fileKey = [unarchiver decodeObject];
507 if ([fileKey isEqual:key]) {
508 id object = [unarchiver decodeObject];
510 // Decoded objects go away when the unarchiver does, so we need to
511 // retain this so we can return it to our caller.
512 result = [[object retain] autorelease];
513 LOG(IconDatabase, "read disk cache file - %@", key);
518 } @catch (NSException *localException) {
519 LOG(IconDatabase, "cannot unarchive cache file - %@", key);
523 [unarchiver release];
530 static NSData* iconDataFromPathForIconURL(NSString *databasePath, NSString *iconURLString)
532 ASSERT(iconURLString);
533 ASSERT(databasePath);
535 NSData *iconData = objectFromPathForKey(databasePath, iconURLString);
537 if ((id)iconData == (id)[NSNull null])
543 - (NSString *)_databaseDirectory
545 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
547 // Figure out the directory we should be using for the icon.db
548 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
549 if (!databaseDirectory) {
550 databaseDirectory = WebIconDatabasePath;
551 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
554 return [[databaseDirectory stringByExpandingTildeInPath] stringByStandardizingPath];
559 @implementation WebIconDatabasePrivate
562 @interface ThreadEnabler : NSObject {
564 + (void)enableThreading;
566 - (void)threadEnablingSelector:(id)arg;
569 @implementation ThreadEnabler
571 - (void)threadEnablingSelector:(id)arg
576 + (void)enableThreading
578 ThreadEnabler *enabler = [[ThreadEnabler alloc] init];
579 [NSThread detachNewThreadSelector:@selector(threadEnablingSelector:) toTarget:enabler withObject:nil];
585 bool importToWebCoreFormat()
587 // Since this is running on a secondary POSIX thread and Cocoa cannot be used multithreaded unless an NSThread has been detached,
588 // make sure that happens here for all WebKit clients
589 if (![NSThread isMultiThreaded])
590 [ThreadEnabler enableThreading];
591 ASSERT([NSThread isMultiThreaded]);
593 // Get the directory the old icon database *should* be in
594 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
595 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseImportDirectoryDefaultsKey];
597 if (!databaseDirectory)
598 databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
600 if (!databaseDirectory) {
601 databaseDirectory = WebIconDatabasePath;
602 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
604 databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
606 // With this directory, get the PageURLToIconURL map that was saved to disk
607 NSMutableDictionary *pageURLToIconURL = objectFromPathForKey(databaseDirectory, WebURLToIconURLKey);
609 // If the retrieved object was not a valid NSMutableDictionary, then we have no valid
611 if (![pageURLToIconURL isKindOfClass:[NSMutableDictionary class]])
612 pageURLToIconURL = nil;
614 if (!pageURLToIconURL) {
615 // We found no Safari-2-style icon database. Bail out immediately and do not delete everything
616 // in whatever directory we ended up looking in! Return true so we won't bother to check again.
617 // FIXME: We can probably delete all of the code to convert Safari-2-style icon databases now.
621 NSEnumerator *enumerator = [pageURLToIconURL keyEnumerator];
622 NSString *url, *iconURL;
624 // First, we'll iterate through the PageURL->IconURL map
625 while ((url = [enumerator nextObject]) != nil) {
626 iconURL = [pageURLToIconURL objectForKey:url];
629 iconDatabase().importIconURLForPageURL(iconURL, url);
630 if (iconDatabase().shouldStopThreadActivity())
634 // Second, we'll get a list of the unique IconURLs we have
635 NSMutableSet *iconsOnDiskWithURLs = [NSMutableSet setWithArray:[pageURLToIconURL allValues]];
636 enumerator = [iconsOnDiskWithURLs objectEnumerator];
639 // And iterate through them, adding the icon data to the new icon database
640 while ((url = [enumerator nextObject]) != nil) {
641 iconData = iconDataFromPathForIconURL(databaseDirectory, url);
643 iconDatabase().importIconDataForIconURL(SharedBuffer::wrapNSData(iconData), url);
645 // This really *shouldn't* happen, so it'd be good to track down why it might happen in a debug build
646 // however, we do know how to handle it gracefully in release
647 LOG_ERROR("%@ is marked as having an icon on disk, but we couldn't get the data for it", url);
648 iconDatabase().importIconDataForIconURL(0, url);
650 if (iconDatabase().shouldStopThreadActivity())
654 // After we're done importing old style icons over to webcore icons, we delete the entire directory hierarchy
655 // for the old icon DB (skipping the new iconDB if it is in the same directory)
656 NSFileManager *fileManager = [NSFileManager defaultManager];
657 enumerator = [[fileManager contentsOfDirectoryAtPath:databaseDirectory error:NULL] objectEnumerator];
659 NSString *databaseFilename = iconDatabase().defaultDatabaseFilename();
661 BOOL foundIconDB = NO;
663 while ((file = [enumerator nextObject]) != nil) {
664 if ([file caseInsensitiveCompare:databaseFilename] == NSOrderedSame) {
668 NSString *filePath = [databaseDirectory stringByAppendingPathComponent:file];
669 if (![fileManager removeItemAtPath:filePath error:NULL])
670 LOG_ERROR("Failed to delete %@ from old icon directory", filePath);
673 // If the new iconDB wasn't in that directory, we can delete the directory itself
675 rmdir([databaseDirectory fileSystemRepresentation]);
680 NSImage *webGetNSImage(Image* image, NSSize size)
682 ASSERT_MAIN_THREAD();
686 // FIXME: We're doing the resize here for now because WebCore::Image doesn't yet support resizing/multiple representations
687 // This makes it so there's effectively only one size of a particular icon in the system at a time. We should move this
688 // to WebCore::Image at some point.
691 NSImage* nsImage = image->getNSImage();
694 if (!NSEqualSizes([nsImage size], size)) {
695 [nsImage setScalesWhenResized:YES];
696 [nsImage setSize:size];