[iOS] Upstream PLATFORM(IOS) changes to Source/WebKit/
[WebKit-https.git] / Source / WebKit / mac / Misc / WebIconDatabase.mm
1 /*
2  * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple 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
29 #if !PLATFORM(IOS)
30
31 #import "WebIconDatabaseInternal.h"
32
33 #import "WebIconDatabaseClient.h"
34 #import "WebIconDatabaseDelegate.h"
35 #import "WebKitLogging.h"
36 #import "WebKitNSStringExtras.h"
37 #import "WebNSFileManagerExtras.h"
38 #import "WebNSURLExtras.h"
39 #import "WebPreferencesPrivate.h"
40 #import "WebTypesInternal.h"
41 #import <WebCore/IconDatabase.h>
42 #import <WebCore/Image.h>
43 #import <WebCore/IntSize.h>
44 #import <WebCore/SharedBuffer.h>
45 #import <WebCore/ThreadCheck.h>
46 #import <runtime/InitializeThreading.h>
47 #import <wtf/MainThread.h>
48 #import <wtf/RunLoop.h>
49
50 using namespace WebCore;
51
52 NSString *WebIconDatabaseDidAddIconNotification =          @"WebIconDatabaseDidAddIconNotification";
53 NSString *WebIconNotificationUserInfoURLKey =              @"WebIconNotificationUserInfoURLKey";
54 NSString *WebIconDatabaseDidRemoveAllIconsNotification =   @"WebIconDatabaseDidRemoveAllIconsNotification";
55
56 NSString *WebIconDatabaseDirectoryDefaultsKey = @"WebIconDatabaseDirectoryDefaultsKey";
57 NSString *WebIconDatabaseEnabledDefaultsKey =   @"WebIconDatabaseEnabled";
58
59 NSString *WebIconDatabasePath = @"~/Library/Icons";
60
61 NSSize WebIconSmallSize = {16, 16};
62 NSSize WebIconMediumSize = {32, 32};
63 NSSize WebIconLargeSize = {128, 128};
64
65 #define UniqueFilePathSize (34)
66
67 static WebIconDatabaseClient* defaultClient()
68 {
69 #if ENABLE(ICONDATABASE)
70     static WebIconDatabaseClient* defaultClient = new WebIconDatabaseClient();
71     return defaultClient;
72 #else
73     return 0;
74 #endif
75 }
76
77 @interface WebIconDatabase (WebReallyInternal)
78 - (void)_sendNotificationForURL:(NSString *)URL;
79 - (void)_sendDidRemoveAllIconsNotification;
80 - (NSImage *)_iconForFileURL:(NSString *)fileURL withSize:(NSSize)size;
81 - (void)_resetCachedWebPreferences:(NSNotification *)notification;
82 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons;
83 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon;
84 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache;
85 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size;
86 - (NSString *)_databaseDirectory;
87 @end
88
89 @implementation WebIconDatabase
90
91 + (void)initialize
92 {
93     JSC::initializeThreading();
94     WTF::initializeMainThreadToProcessMainThread();
95     RunLoop::initializeMainRunLoop();
96 }
97
98 + (WebIconDatabase *)sharedIconDatabase
99 {
100     static WebIconDatabase *database = nil;
101     if (!database)
102         database = [[WebIconDatabase alloc] init];
103     return database;
104 }
105
106 - (id)init
107 {
108     self = [super init];
109     if (!self)
110         return nil;
111     WebCoreThreadViolationCheckRoundOne();
112         
113     _private = [[WebIconDatabasePrivate alloc] init];
114     
115     // Check the user defaults and see if the icon database should even be enabled.
116     // Inform the bridge and, if we're disabled, bail from init right here
117     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
118     // <rdar://problem/4741419> - IconDatabase should be disabled by default
119     NSDictionary *initialDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithBool:YES], WebIconDatabaseEnabledDefaultsKey, nil];
120     [defaults registerDefaults:initialDefaults];
121     [initialDefaults release];
122     BOOL enabled = [defaults boolForKey:WebIconDatabaseEnabledDefaultsKey];
123     iconDatabase().setEnabled(enabled);
124     if (enabled)
125         [self _startUpIconDatabase];
126     return self;
127 }
128
129 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size cache:(BOOL)cache
130 {
131     ASSERT_MAIN_THREAD();
132     ASSERT(size.width);
133     ASSERT(size.height);
134
135     if (!URL || ![self isEnabled])
136         return [self defaultIconForURL:URL withSize:size];
137
138     // FIXME - <rdar://problem/4697934> - Move the handling of FileURLs to WebCore and implement in ObjC++
139     if ([URL _webkit_isFileURL])
140         return [self _iconForFileURL:URL withSize:size];
141     
142     if (Image* image = iconDatabase().synchronousIconForPageURL(URL, IntSize(size)))
143         if (NSImage *icon = webGetNSImage(image, size))
144             return icon;
145     return [self defaultIconForURL:URL withSize:size];
146 }
147
148 - (NSImage *)iconForURL:(NSString *)URL withSize:(NSSize)size
149 {
150     return [self iconForURL:URL withSize:size cache:YES];
151 }
152
153 - (NSString *)iconURLForURL:(NSString *)URL
154 {
155     if (![self isEnabled])
156         return nil;
157     ASSERT_MAIN_THREAD();
158
159     return iconDatabase().synchronousIconURLForPageURL(URL);
160 }
161
162 - (NSImage *)defaultIconWithSize:(NSSize)size
163 {
164     ASSERT_MAIN_THREAD();
165     ASSERT(size.width);
166     ASSERT(size.height);
167     
168     Image* image = iconDatabase().defaultIcon(IntSize(size));
169     return image ? image->getNSImage() : nil;
170 }
171
172 - (NSImage *)defaultIconForURL:(NSString *)URL withSize:(NSSize)size
173 {
174     if (_private->delegateImplementsDefaultIconForURL)
175         return [_private->delegate webIconDatabase:self defaultIconForURL:URL withSize:size];
176     return [self defaultIconWithSize:size];
177 }
178
179 - (void)retainIconForURL:(NSString *)URL
180 {
181     ASSERT_MAIN_THREAD();
182     ASSERT(URL);
183     if (![self isEnabled])
184         return;
185
186     iconDatabase().retainIconForPageURL(URL);
187 }
188
189 - (void)releaseIconForURL:(NSString *)pageURL
190 {
191     ASSERT_MAIN_THREAD();
192     ASSERT(pageURL);
193     if (![self isEnabled])
194         return;
195
196     iconDatabase().releaseIconForPageURL(pageURL);
197 }
198
199 + (void)delayDatabaseCleanup
200 {
201     ASSERT_MAIN_THREAD();
202
203     IconDatabase::delayDatabaseCleanup();
204 }
205
206 + (void)allowDatabaseCleanup
207 {
208     ASSERT_MAIN_THREAD();
209
210     IconDatabase::allowDatabaseCleanup();
211 }
212
213 - (void)setDelegate:(id)delegate
214 {
215     _private->delegate = delegate;
216     _private->delegateImplementsDefaultIconForURL = [delegate respondsToSelector:@selector(webIconDatabase:defaultIconForURL:withSize:)];
217 }
218
219 - (id)delegate
220 {
221     return _private->delegate;
222 }
223
224 @end
225
226
227 @implementation WebIconDatabase (WebPendingPublic)
228
229 - (BOOL)isEnabled
230 {
231     return iconDatabase().isEnabled();
232 }
233
234 - (void)setEnabled:(BOOL)flag
235 {
236     BOOL currentlyEnabled = [self isEnabled];
237     if (currentlyEnabled && !flag) {
238         iconDatabase().setEnabled(false);
239         [self _shutDownIconDatabase];
240     } else if (!currentlyEnabled && flag) {
241         iconDatabase().setEnabled(true);
242         [self _startUpIconDatabase];
243     }
244 }
245
246 - (void)removeAllIcons
247 {
248     ASSERT_MAIN_THREAD();
249     if (![self isEnabled])
250         return;
251
252     // Via the IconDatabaseClient interface, removeAllIcons() will send the WebIconDatabaseDidRemoveAllIconsNotification
253     iconDatabase().removeAllIcons();
254 }
255
256 @end
257
258 @implementation WebIconDatabase (WebPrivate)
259
260 + (void)_checkIntegrityBeforeOpening
261 {
262     IconDatabase::checkIntegrityBeforeOpening();
263 }
264
265 @end
266
267 @implementation WebIconDatabase (WebInternal)
268
269 - (void)_sendNotificationForURL:(NSString *)URL
270 {
271     ASSERT(URL);
272     
273     dispatch_async(dispatch_get_main_queue(), ^{
274         NSDictionary *userInfo = @{ WebIconNotificationUserInfoURLKey : URL };
275         [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidAddIconNotification object:self userInfo:userInfo];
276     });
277 }
278
279 - (void)_sendDidRemoveAllIconsNotification
280 {
281     dispatch_async(dispatch_get_main_queue(), ^{
282         [[NSNotificationCenter defaultCenter] postNotificationName:WebIconDatabaseDidRemoveAllIconsNotification object:self];
283     });
284 }
285
286 - (void)_startUpIconDatabase
287 {
288     iconDatabase().setClient(defaultClient());
289     
290     // Figure out the directory we should be using for the icon.db
291     NSString *databaseDirectory = [self _databaseDirectory];
292     
293     // Rename legacy icon database files to the new icon database name
294     BOOL isDirectory = NO;
295     NSString *legacyDB = [databaseDirectory stringByAppendingPathComponent:@"icon.db"];
296     NSFileManager *defaultManager = [NSFileManager defaultManager];
297     if ([defaultManager fileExistsAtPath:legacyDB isDirectory:&isDirectory] && !isDirectory) {
298         NSString *newDB = [databaseDirectory stringByAppendingPathComponent:IconDatabase::defaultDatabaseFilename()];
299         if (![defaultManager fileExistsAtPath:newDB])
300             rename([legacyDB fileSystemRepresentation], [newDB fileSystemRepresentation]);
301     }
302     
303     // Set the private browsing pref then open the WebCore icon database
304     iconDatabase().setPrivateBrowsingEnabled([[WebPreferences standardPreferences] privateBrowsingEnabled]);
305     if (!iconDatabase().open(databaseDirectory, IconDatabase::defaultDatabaseFilename()))
306         LOG_ERROR("Unable to open icon database");
307     
308     // Register for important notifications
309     [[NSNotificationCenter defaultCenter] addObserver:self
310                                              selector:@selector(_applicationWillTerminate:)
311                                                  name:NSApplicationWillTerminateNotification
312                                                object:NSApp];
313     [[NSNotificationCenter defaultCenter] addObserver:self
314                                              selector:@selector(_resetCachedWebPreferences:)
315                                                  name:WebPreferencesChangedInternalNotification
316                                                object:nil];
317 }
318
319 - (void)_shutDownIconDatabase
320 {
321     // Unregister for important notifications
322     [[NSNotificationCenter defaultCenter] removeObserver:self
323                                                     name:NSApplicationWillTerminateNotification
324                                                   object:NSApp];
325     [[NSNotificationCenter defaultCenter] removeObserver:self
326                                                     name:WebPreferencesChangedInternalNotification
327                                                   object:nil];
328 }
329
330 - (void)_applicationWillTerminate:(NSNotification *)notification
331 {
332     iconDatabase().close();
333 }
334
335 - (NSImage *)_iconForFileURL:(NSString *)file withSize:(NSSize)size
336 {
337     ASSERT_MAIN_THREAD();
338     ASSERT(size.width);
339     ASSERT(size.height);
340
341     NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
342     NSString *path = [[NSURL _web_URLWithDataAsString:file] path];
343     NSString *suffix = [path pathExtension];
344     NSImage *icon = nil;
345     
346     if ([suffix _webkit_isCaseInsensitiveEqualToString:@"htm"] || [suffix _webkit_isCaseInsensitiveEqualToString:@"html"]) {
347         if (!_private->htmlIcons) {
348             icon = [workspace iconForFileType:@"html"];
349             _private->htmlIcons = [[self _iconsBySplittingRepresentationsOfIcon:icon] retain];
350         }
351         icon = [self _iconFromDictionary:_private->htmlIcons forSize:size cache:YES];
352     } else {
353         if (!path || ![path isAbsolutePath]) {
354             // Return the generic icon when there is no path.
355             icon = [workspace iconForFileType:NSFileTypeForHFSTypeCode(kGenericDocumentIcon)];
356         } else {
357             icon = [workspace iconForFile:path];
358         }
359         [self _scaleIcon:icon toSize:size];
360     }
361
362     return icon;
363 }
364
365 - (void)_resetCachedWebPreferences:(NSNotification *)notification
366 {
367     BOOL privateBrowsingEnabledNow = [[WebPreferences standardPreferences] privateBrowsingEnabled];
368     iconDatabase().setPrivateBrowsingEnabled(privateBrowsingEnabledNow);
369 }
370
371 - (NSImage *)_largestIconFromDictionary:(NSMutableDictionary *)icons
372 {
373     ASSERT(icons);
374     
375     NSEnumerator *enumerator = [icons keyEnumerator];
376     NSValue *currentSize, *largestSize=nil;
377     float largestSizeArea=0;
378
379     while ((currentSize = [enumerator nextObject]) != nil) {
380         NSSize currentSizeSize = [currentSize sizeValue];
381         float currentSizeArea = currentSizeSize.width * currentSizeSize.height;
382         if(!largestSizeArea || (currentSizeArea > largestSizeArea)){
383             largestSize = currentSize;
384             largestSizeArea = currentSizeArea;
385         }
386     }
387
388     return [icons objectForKey:largestSize];
389 }
390
391 - (NSMutableDictionary *)_iconsBySplittingRepresentationsOfIcon:(NSImage *)icon
392 {
393     ASSERT(icon);
394
395     NSMutableDictionary *icons = [NSMutableDictionary dictionary];
396     NSEnumerator *enumerator = [[icon representations] objectEnumerator];
397     NSImageRep *rep;
398
399     while ((rep = [enumerator nextObject]) != nil) {
400         NSSize size = [rep size];
401         NSImage *subIcon = [[NSImage alloc] initWithSize:size];
402         [subIcon addRepresentation:rep];
403         [icons setObject:subIcon forKey:[NSValue valueWithSize:size]];
404         [subIcon release];
405     }
406
407     if([icons count] > 0)
408         return icons;
409
410     LOG_ERROR("icon has no representations");
411     
412     return nil;
413 }
414
415 - (NSImage *)_iconFromDictionary:(NSMutableDictionary *)icons forSize:(NSSize)size cache:(BOOL)cache
416 {
417     ASSERT(size.width);
418     ASSERT(size.height);
419
420     NSImage *icon = [icons objectForKey:[NSValue valueWithSize:size]];
421
422     if(!icon){
423         icon = [[[self _largestIconFromDictionary:icons] copy] autorelease];
424         [self _scaleIcon:icon toSize:size];
425
426         if(cache){
427             [icons setObject:icon forKey:[NSValue valueWithSize:size]];
428         }
429     }
430
431     return icon;
432 }
433
434 - (void)_scaleIcon:(NSImage *)icon toSize:(NSSize)size
435 {
436     ASSERT(size.width);
437     ASSERT(size.height);
438     
439 #if !LOG_DISABLED
440     double start = CFAbsoluteTimeGetCurrent();
441 #endif
442     
443 #pragma clang diagnostic push
444 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
445     [icon setScalesWhenResized:YES];
446 #pragma clang diagnostic pop
447     [icon setSize:size];
448     
449 #if !LOG_DISABLED
450     double duration = CFAbsoluteTimeGetCurrent() - start;
451     LOG(Timing, "scaling icon took %f seconds.", duration);
452 #endif
453 }
454
455 - (NSString *)_databaseDirectory
456 {
457     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
458
459     // Figure out the directory we should be using for the icon.db
460     NSString *databaseDirectory = [defaults objectForKey:WebIconDatabaseDirectoryDefaultsKey];
461     if (!databaseDirectory) {
462         databaseDirectory = WebIconDatabasePath;
463         [defaults setObject:databaseDirectory forKey:WebIconDatabaseDirectoryDefaultsKey];
464     }
465     
466     return [[databaseDirectory stringByExpandingTildeInPath] stringByStandardizingPath];
467 }
468
469 @end
470
471 @implementation WebIconDatabasePrivate
472 @end
473
474 NSImage *webGetNSImage(Image* image, NSSize size)
475 {
476     ASSERT_MAIN_THREAD();
477     ASSERT(size.width);
478     ASSERT(size.height);
479
480     // FIXME: We're doing the resize here for now because WebCore::Image doesn't yet support resizing/multiple representations
481     // This makes it so there's effectively only one size of a particular icon in the system at a time. We should move this
482     // to WebCore::Image at some point.
483     if (!image)
484         return nil;
485     NSImage* nsImage = image->getNSImage();
486     if (!nsImage)
487         return nil;
488     if (!NSEqualSizes([nsImage size], size)) {
489 #pragma clang diagnostic push
490 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
491         [nsImage setScalesWhenResized:YES];
492 #pragma clang diagnostic pop
493         [nsImage setSize:size];
494     }
495     return nsImage;
496 }
497
498 #endif // !PLATFORM(IOS)