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 "WebPreferencesPrivate.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/MainThread.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];
112 WebCoreThreadViolationCheckRoundOne();
114 _private = [[WebIconDatabasePrivate alloc] init];
116 // Check the user defaults and see if the icon database should even be enabled.
117 // Inform the bridge and, if we're disabled, bail from init right here
118 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
119 // <rdar://problem/4741419> - IconDatabase should be disabled by default
120 NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
121 [defaults registerDefaults:initialDefaults];
122 [initialDefaults release];
123 BOOL enabled = [defaults boolForKey:WebIconDatabaseEnabledDefaultsKey];
124 iconDatabase().setEnabled(enabled);
126 [self _startUpIconDatabase];
130 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
132 ASSERT_MAIN_THREAD();
136 if (!URL || ![self isEnabled])
137 return [self defaultIconForURL:URL withSize:size];
139 // FIXME - <rdar://problem/4697934> - Move the handling of FileURLs to WebCore and implement in ObjC++
140 if ([URL _webkit_isFileURL])
141 return [self _iconForFileURL:URL withSize:size];
143 if (Image* image = iconDatabase().synchronousIconForPageURL(URL, IntSize(size)))
144 if (NSImage *icon = webGetNSImage(image, size))
146 return [self defaultIconForURL:URL withSize:size];
149 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
151 return [self iconForURL:URL withSize:size cache:YES];
154 - (NSString *)iconURLForURL:(NSString *)URL
156 if (![self isEnabled])
158 ASSERT_MAIN_THREAD();
160 return iconDatabase().synchronousIconURLForPageURL(URL);
163 - (NSImage *)defaultIconWithSize:(NSSize)size
165 ASSERT_MAIN_THREAD();
169 Image* image = iconDatabase().defaultIcon(IntSize(size));
170 return image ? image->getNSImage() : nil;
173 - (NSImage *)defaultIconForURL:(NSString *)URL withSize:(NSSize)size
175 if (_private->delegateImplementsDefaultIconForURL)
176 return [_private->delegate webIconDatabase:self defaultIconForURL:URL withSize:size];
177 return [self defaultIconWithSize:size];
180 - (void)retainIconForURL:(NSString *)URL
182 ASSERT_MAIN_THREAD();
184 if (![self isEnabled])
187 iconDatabase().retainIconForPageURL(URL);
190 - (void)releaseIconForURL:(NSString *)pageURL
192 ASSERT_MAIN_THREAD();
194 if (![self isEnabled])
197 iconDatabase().releaseIconForPageURL(pageURL);
200 + (void)delayDatabaseCleanup
202 ASSERT_MAIN_THREAD();
204 IconDatabase::delayDatabaseCleanup();
207 + (void)allowDatabaseCleanup
209 ASSERT_MAIN_THREAD();
211 IconDatabase::allowDatabaseCleanup();
214 - (void)setDelegate:(id)delegate
216 _private->delegate = delegate;
217 _private->delegateImplementsDefaultIconForURL = [delegate respondsToSelector:@selector(webIconDatabase:defaultIconForURL:withSize:)];
222 return _private->delegate;
228 @implementation WebIconDatabase (WebPendingPublic)
232 return iconDatabase().isEnabled();
235 - (void)setEnabled:(BOOL)flag
237 BOOL currentlyEnabled = [self isEnabled];
238 if (currentlyEnabled && !flag) {
239 iconDatabase().setEnabled(false);
240 [self _shutDownIconDatabase];
241 } else if (!currentlyEnabled && flag) {
242 iconDatabase().setEnabled(true);
243 [self _startUpIconDatabase];
247 - (void)removeAllIcons
249 ASSERT_MAIN_THREAD();
250 if (![self isEnabled])
253 // Via the IconDatabaseClient interface, removeAllIcons() will send the WebIconDatabaseDidRemoveAllIconsNotification
254 iconDatabase().removeAllIcons();
259 @implementation WebIconDatabase (WebPrivate)
261 + (void)_checkIntegrityBeforeOpening
263 IconDatabase::checkIntegrityBeforeOpening();
268 @implementation WebIconDatabase (WebInternal)
270 - (void)_sendNotificationForURL:(NSString *)URL
274 NSDictionary *userInfo = [NSDictionary dictionaryWithObject:URL
275 forKey:WebIconNotificationUserInfoURLKey];
277 [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:WebIconDatabaseDidAddIconNotification
282 - (void)_sendDidRemoveAllIconsNotification
284 [[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:WebIconDatabaseDidRemoveAllIconsNotification
289 - (void)_startUpIconDatabase
291 iconDatabase().setClient(defaultClient());
293 // Figure out the directory we should be using for the icon.db
294 NSString *databaseDirectory = [self _databaseDirectory];
296 // Rename legacy icon database files to the new icon database name
297 BOOL isDirectory = NO;
298 NSString *legacyDB = [databaseDirectory stringByAppendingPathComponent:@"icon.db"];
299 NSFileManager *defaultManager = [NSFileManager defaultManager];
300 if ([defaultManager fileExistsAtPath:legacyDB isDirectory:&isDirectory] && !isDirectory) {
301 NSString *newDB = [databaseDirectory stringByAppendingPathComponent:IconDatabase::defaultDatabaseFilename()];
302 if (![defaultManager fileExistsAtPath:newDB])
303 rename([legacyDB fileSystemRepresentation], [newDB fileSystemRepresentation]);
306 // Set the private browsing pref then open the WebCore icon database
307 iconDatabase().setPrivateBrowsingEnabled([[WebPreferences standardPreferences] privateBrowsingEnabled]);
308 if (!iconDatabase().open(databaseDirectory, IconDatabase::defaultDatabaseFilename()))
309 LOG_ERROR("Unable to open icon database");
311 // Register for important notifications
312 [[NSNotificationCenter defaultCenter] addObserver:self
313 selector:@selector(_applicationWillTerminate:)
314 name:NSApplicationWillTerminateNotification
316 [[NSNotificationCenter defaultCenter] addObserver:self
317 selector:@selector(_resetCachedWebPreferences:)
318 name:WebPreferencesChangedInternalNotification
322 - (void)_shutDownIconDatabase
324 // Unregister for important notifications
325 [[NSNotificationCenter defaultCenter] removeObserver:self
326 name:NSApplicationWillTerminateNotification
328 [[NSNotificationCenter defaultCenter] removeObserver:self
329 name:WebPreferencesChangedInternalNotification
333 - (void)_applicationWillTerminate:(NSNotification *)notification
335 iconDatabase().close();
338 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
340 ASSERT_MAIN_THREAD();
344 NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
345 NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
346 NSString *suffix = [path pathExtension];
349 if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
350 if (!_private->htmlIcons) {
351 icon = [workspace iconForFileType:@"html"];
352 _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
354 icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
356 if (!path || ![path isAbsolutePath]) {
357 // Return the generic icon when there is no path.
358 icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
360 icon = [workspace iconForFile:path];
362 [self _scaleIcon:icon toSize:size];
368 - (void)_resetCachedWebPreferences:(NSNotification *)notification
370 BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
371 iconDatabase().setPrivateBrowsingEnabled(privateBrowsingEnabledNow);
374 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
378 NSEnumerator *enumerator = [icons keyEnumerator];
379 NSValue *currentSize, *largestSize=nil;
380 float largestSizeArea=0;
382 while ((currentSize = [enumerator nextObject]) != nil) {
383 NSSize currentSizeSize = [currentSize sizeValue];
384 float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
385 if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
386 largestSize = currentSize;
387 largestSizeArea = currentSizeArea;
391 return [icons objectForKey:largestSize];
394 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
398 NSMutableDictionary *icons = [NSMutableDictionary dictionary];
399 NSEnumerator *enumerator = [[icon representations] objectEnumerator];
402 while ((rep = [enumerator nextObject]) != nil) {
403 NSSize size = [rep size];
404 NSImage *subIcon = [[NSImage alloc] initWithSize:size];
405 [subIcon addRepresentation:rep];
406 [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
410 if([icons count] > 0)
413 LOG_ERROR("icon has no representations");
418 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
423 NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
426 icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
427 [self _scaleIcon:icon toSize:size];
430 [icons setObject:icon forKey:[NSValue valueWithSize:size]];
437 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
443 double start = CFAbsoluteTimeGetCurrent();
446 [icon setScalesWhenResized:YES];
450 double duration = CFAbsoluteTimeGetCurrent() - start;
451 LOG(Timing, "scaling icon took %f seconds.", duration);
455 // This hashing String->filename algorithm came from WebFileDatabase.m and is what was used in the
456 // WebKit Icon Database
457 static void legacyIconDatabaseFilePathForKey(id key, char *buffer)
465 s = [[[[key description] lowercaseString] stringByStandardizingPath] UTF8String];
468 // compute first hash
470 for (cnt = 0; cnt < len; cnt++) {
471 hash1 += (hash1 << 8) + s[cnt];
473 hash1 += (hash1 << (len & 31));
475 // compute second hash
477 for (cnt = 0; cnt < len; cnt++) {
478 hash2 = (37 * hash2) ^ s[cnt];
482 snprintf(buffer, UniqueFilePathSize, "%.2u/%.2u/%.10u-%.10u.cache", ((hash1 & 0xff) >> 4), ((hash2 & 0xff) >> 4), hash1, hash2);
484 snprintf(buffer, UniqueFilePathSize, "%.2lu/%.2lu/%.10lu-%.10lu.cache", ((hash1 & 0xff) >> 4), ((hash2 & 0xff) >> 4), hash1, hash2);
488 // This method of getting an object from the filesystem is taken from the old
489 // WebKit Icon Database
490 static id objectFromPathForKey(NSString *databasePath, id key)
495 // Use the key->filename hashing the old WebKit IconDatabase used
496 char uniqueKey[UniqueFilePathSize];
497 legacyIconDatabaseFilePathForKey(key, uniqueKey);
499 // Get the data from this file and setup for the un-archiving
500 NSString *filePath = [[NSString alloc] initWithFormat:@"%@/%s", databasePath, uniqueKey];
501 NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
502 NSUnarchiver *unarchiver = nil;
506 unarchiver = [[NSUnarchiver alloc] initForReadingWithData:data];
508 id fileKey = [unarchiver decodeObject];
509 if ([fileKey isEqual:key]) {
510 id object = [unarchiver decodeObject];
512 // Decoded objects go away when the unarchiver does, so we need to
513 // retain this so we can return it to our caller.
514 result = [[object retain] autorelease];
515 LOG(IconDatabase, "read disk cache file - %@", key);
520 } @catch (NSException *localException) {
521 LOG(IconDatabase, "cannot unarchive cache file - %@", key);
525 [unarchiver release];
532 static NSData* iconDataFromPathForIconURL(NSString *databasePath, NSString *iconURLString)
534 ASSERT(iconURLString);
535 ASSERT(databasePath);
537 NSData *iconData = objectFromPathForKey(databasePath, iconURLString);
539 if ((id)iconData == (id)[NSNull null])
545 - (NSString *)_databaseDirectory
547 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
549 // Figure out the directory we should be using for the icon.db
550 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
551 if (!databaseDirectory) {
552 databaseDirectory = WebIconDatabasePath;
553 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
556 return [[databaseDirectory stringByExpandingTildeInPath] stringByStandardizingPath];
561 @implementation WebIconDatabasePrivate
564 @interface ThreadEnabler : NSObject {
566 + (void)enableThreading;
568 - (void)threadEnablingSelector:(id)arg;
571 @implementation ThreadEnabler
573 - (void)threadEnablingSelector:(id)arg
578 + (void)enableThreading
580 ThreadEnabler *enabler = [[ThreadEnabler alloc] init];
581 [NSThread detachNewThreadSelector:@selector(threadEnablingSelector:) toTarget:enabler withObject:nil];
587 bool importToWebCoreFormat()
589 // Since this is running on a secondary POSIX thread and Cocoa cannot be used multithreaded unless an NSThread has been detached,
590 // make sure that happens here for all WebKit clients
591 if (![NSThread isMultiThreaded])
592 [ThreadEnabler enableThreading];
593 ASSERT([NSThread isMultiThreaded]);
595 // Get the directory the old icon database *should* be in
596 NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
597 NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseImportDirectoryDefaultsKey];
599 if (!databaseDirectory)
600 databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
602 if (!databaseDirectory) {
603 databaseDirectory = WebIconDatabasePath;
604 [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
606 databaseDirectory = [databaseDirectory stringByExpandingTildeInPath];
608 // With this directory, get the PageURLToIconURL map that was saved to disk
609 NSMutableDictionary *pageURLToIconURL = objectFromPathForKey(databaseDirectory, WebURLToIconURLKey);
611 // If the retrieved object was not a valid NSMutableDictionary, then we have no valid
613 if (![pageURLToIconURL isKindOfClass:[NSMutableDictionary class]])
614 pageURLToIconURL = nil;
616 if (!pageURLToIconURL) {
617 // We found no Safari-2-style icon database. Bail out immediately and do not delete everything
618 // in whatever directory we ended up looking in! Return true so we won't bother to check again.
619 // FIXME: We can probably delete all of the code to convert Safari-2-style icon databases now.
623 NSEnumerator *enumerator = [pageURLToIconURL keyEnumerator];
624 NSString *url, *iconURL;
626 // First, we'll iterate through the PageURL->IconURL map
627 while ((url = [enumerator nextObject]) != nil) {
628 iconURL = [pageURLToIconURL objectForKey:url];
631 iconDatabase().importIconURLForPageURL(iconURL, url);
632 if (iconDatabase().shouldStopThreadActivity())
636 // Second, we'll get a list of the unique IconURLs we have
637 NSMutableSet *iconsOnDiskWithURLs = [NSMutableSet setWithArray:[pageURLToIconURL allValues]];
638 enumerator = [iconsOnDiskWithURLs objectEnumerator];
641 // And iterate through them, adding the icon data to the new icon database
642 while ((url = [enumerator nextObject]) != nil) {
643 iconData = iconDataFromPathForIconURL(databaseDirectory, url);
645 iconDatabase().importIconDataForIconURL(SharedBuffer::wrapNSData(iconData), url);
647 // This really *shouldn't* happen, so it'd be good to track down why it might happen in a debug build
648 // however, we do know how to handle it gracefully in release
649 LOG_ERROR("%@ is marked as having an icon on disk, but we couldn't get the data for it", url);
650 iconDatabase().importIconDataForIconURL(0, url);
652 if (iconDatabase().shouldStopThreadActivity())
656 // After we're done importing old style icons over to webcore icons, we delete the entire directory hierarchy
657 // for the old icon DB (skipping the new iconDB if it is in the same directory)
658 NSFileManager *fileManager = [NSFileManager defaultManager];
659 enumerator = [[fileManager contentsOfDirectoryAtPath:databaseDirectory error:NULL] objectEnumerator];
661 NSString *databaseFilename = IconDatabase::defaultDatabaseFilename();
663 BOOL foundIconDB = NO;
665 while ((file = [enumerator nextObject]) != nil) {
666 if ([file caseInsensitiveCompare:databaseFilename] == NSOrderedSame) {
670 NSString *filePath = [databaseDirectory stringByAppendingPathComponent:file];
671 if (![fileManager removeItemAtPath:filePath error:NULL])
672 LOG_ERROR("Failed to delete %@ from old icon directory", filePath);
675 // If the new iconDB wasn't in that directory, we can delete the directory itself
677 rmdir([databaseDirectory fileSystemRepresentation]);
682 NSImage *webGetNSImage(Image* image, NSSize size)
684 ASSERT_MAIN_THREAD();
688 // FIXME: We're doing the resize here for now because WebCore::Image doesn't yet support resizing/multiple representations
689 // This makes it so there's effectively only one size of a particular icon in the system at a time. We should move this
690 // to WebCore::Image at some point.
693 NSImage* nsImage = image->getNSImage();
696 if (!NSEqualSizes([nsImage size], size)) {
697 [nsImage setScalesWhenResized:YES];
698 [nsImage setSize:size];