[Cocoa] More tweaks and refactoring to prepare for ARC
[WebKit.git] / Source / WebKitLegacy / mac / Plugins / WebPluginDatabase.mm
1 /*
2  * Copyright (C) 2005 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 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 #import "WebPluginDatabase.h"
30
31 #import "WebBaseNetscapePluginView.h"
32 #import "WebBasePluginPackage.h"
33 #import "WebDataSourcePrivate.h"
34 #import "WebFrame.h"
35 #import "WebFrameViewInternal.h"
36 #import "WebHTMLRepresentation.h"
37 #import "WebHTMLView.h"
38 #import "WebKitLogging.h"
39 #import "WebNSFileManagerExtras.h"
40 #import "WebNetscapePluginPackage.h"
41 #import "WebPluginController.h"
42 #import "WebPluginPackage.h"
43 #import "WebViewInternal.h"
44 #import "WebViewPrivate.h"
45 #import <pal/spi/cocoa/NSURLFileTypeMappingsSPI.h>
46 #import <wtf/Assertions.h>
47
48 #if PLATFORM(IOS)
49 #import "WebUIKitSupport.h"
50 #endif
51
52 using namespace WebCore;
53
54 static void checkCandidate(WebBasePluginPackage **currentPlugin, WebBasePluginPackage **candidatePlugin);
55
56 @interface WebPluginDatabase (Internal)
57 + (NSArray *)_defaultPlugInPaths;
58 - (NSArray *)_plugInPaths;
59 - (void)_addPlugin:(WebBasePluginPackage *)plugin;
60 - (void)_removePlugin:(WebBasePluginPackage *)plugin;
61 - (NSMutableSet *)_scanForNewPlugins;
62 @end
63
64 @implementation WebPluginDatabase
65
66 static WebPluginDatabase *sharedDatabase = nil;
67
68 + (WebPluginDatabase *)sharedDatabase 
69 {
70     if (!sharedDatabase) {
71         sharedDatabase = [[WebPluginDatabase alloc] init];
72         [sharedDatabase setPlugInPaths:[self _defaultPlugInPaths]];
73         [sharedDatabase refresh];
74     }
75     
76     return sharedDatabase;
77 }
78
79 + (WebPluginDatabase *)sharedDatabaseIfExists
80 {
81     return sharedDatabase;
82 }
83
84 + (void)closeSharedDatabase 
85 {
86     [sharedDatabase close];
87 }
88
89 static void checkCandidate(WebBasePluginPackage * __strong *currentPlugin, WebBasePluginPackage * __strong *candidatePlugin)
90 {
91     if (!*currentPlugin) {
92         *currentPlugin = *candidatePlugin;
93         return;
94     }
95
96     if ([*currentPlugin bundleIdentifier] == [*candidatePlugin bundleIdentifier] && [*candidatePlugin versionNumber] > [*currentPlugin versionNumber]) 
97         *currentPlugin = *candidatePlugin;
98 }
99
100 struct PluginPackageCandidates {
101     PluginPackageCandidates()
102         : webPlugin(nil)
103         , netscapePlugin(nil)
104     {
105     }
106     
107     void update(WebBasePluginPackage *plugin)
108     {
109         if ([plugin isKindOfClass:[WebPluginPackage class]]) {
110             checkCandidate(&webPlugin, &plugin);
111             return;
112         }
113             
114 #if ENABLE(NETSCAPE_PLUGIN_API)
115         if([plugin isKindOfClass:[WebNetscapePluginPackage class]]) {
116             checkCandidate(&netscapePlugin, &plugin);
117             return;
118         }
119 #endif
120         ASSERT_NOT_REACHED();
121     }
122     
123     WebBasePluginPackage *bestCandidate()
124     {
125         // Allow other plug-ins to win over QT because if the user has installed a plug-in that can handle a type
126         // that the QT plug-in can handle, they probably intended to override QT.
127         if (webPlugin && ![webPlugin isQuickTimePlugIn])
128             return webPlugin;
129     
130         if (netscapePlugin && ![netscapePlugin isQuickTimePlugIn])
131             return netscapePlugin;
132         
133         if (webPlugin)
134             return webPlugin;
135         if (netscapePlugin)
136             return netscapePlugin;
137
138         return nil;
139     }
140     
141     WebBasePluginPackage *webPlugin;
142     WebBasePluginPackage *netscapePlugin;
143 };
144
145 - (WebBasePluginPackage *)pluginForMIMEType:(NSString *)MIMEType
146 {
147     PluginPackageCandidates candidates;
148     
149     MIMEType = [MIMEType lowercaseString];
150     NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
151     
152     while (WebBasePluginPackage *plugin = [pluginEnumerator nextObject]) {
153         if ([plugin supportsMIMEType:MIMEType])
154             candidates.update(plugin);
155     }
156     
157     return candidates.bestCandidate();
158 }
159
160 - (WebBasePluginPackage *)pluginForExtension:(NSString *)extension
161 {
162     PluginPackageCandidates candidates;
163     
164     extension = [extension lowercaseString];
165     NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
166     
167     while (WebBasePluginPackage *plugin = [pluginEnumerator nextObject]) {
168         if ([plugin supportsExtension:extension])
169             candidates.update(plugin);
170     }
171     
172     WebBasePluginPackage *plugin = candidates.bestCandidate();
173     
174     if (!plugin) {
175         // If no plug-in was found from the extension, attempt to map from the extension to a MIME type
176         // and find the a plug-in from the MIME type. This is done in case the plug-in has not fully specified
177         // an extension <-> MIME type mapping.
178         NSString *MIMEType = [[NSURLFileTypeMappings sharedMappings] MIMETypeForExtension:extension];
179         if ([MIMEType length] > 0)
180             plugin = [self pluginForMIMEType:MIMEType];
181     }
182     return plugin;
183 }
184
185 - (NSArray *)plugins
186 {
187     return [plugins allValues];
188 }
189
190 static NSArray *additionalWebPlugInPaths;
191
192 + (void)setAdditionalWebPlugInPaths:(NSArray *)additionalPaths
193 {
194     if (additionalPaths == additionalWebPlugInPaths)
195         return;
196     
197     [additionalWebPlugInPaths release];
198     additionalWebPlugInPaths = [additionalPaths copy];
199
200     // One might be tempted to add additionalWebPlugInPaths to the global WebPluginDatabase here.
201     // For backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI,
202     // we need to save a copy of the additional paths and not cause a refresh of the plugin DB
203     // at this time.
204     // See Radars 4608487 and 4609047.
205 }
206
207 - (void)setPlugInPaths:(NSArray *)newPaths
208 {
209     if (plugInPaths == newPaths)
210         return;
211         
212     [plugInPaths release];
213     plugInPaths = [newPaths copy];
214 }
215
216 - (void)close
217 {
218     NSEnumerator *pluginEnumerator = [[self plugins] objectEnumerator];
219     WebBasePluginPackage *plugin;
220     while ((plugin = [pluginEnumerator nextObject]) != nil)
221         [self _removePlugin:plugin];
222     [plugins release];
223     plugins = nil;
224 }
225
226 - (id)init
227 {
228     if (!(self = [super init]))
229         return nil;
230         
231     registeredMIMETypes = [[NSMutableSet alloc] init];
232     pluginInstanceViews = [[NSMutableSet alloc] init];
233     
234     return self;
235 }
236
237 - (void)dealloc
238 {
239     [plugInPaths release];
240     [plugins release];
241     [registeredMIMETypes release];
242     [pluginInstanceViews release];
243     
244     [super dealloc];
245 }
246
247 - (void)refresh
248 {
249     // This method does a bit of autoreleasing, so create an autorelease pool to ensure that calling
250     // -refresh multiple times does not bloat the default pool.
251     @autoreleasepool {
252         // Create map from plug-in path to WebBasePluginPackage
253         if (!plugins)
254             plugins = [[NSMutableDictionary alloc] initWithCapacity:12];
255
256         // Find all plug-ins on disk
257         NSMutableSet *newPlugins = [self _scanForNewPlugins];
258
259         // Find plug-ins to remove from database (i.e., plug-ins that no longer exist on disk)
260         NSMutableSet *pluginsToRemove = [NSMutableSet set];
261         NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
262         WebBasePluginPackage *plugin;
263         while ((plugin = [pluginEnumerator nextObject]) != nil) {
264             // Any plug-ins that were removed from disk since the last refresh should be removed from
265             // the database.
266             if (![newPlugins containsObject:plugin])
267                 [pluginsToRemove addObject:plugin];
268
269             // Remove every member of 'plugins' from 'newPlugins'. After this loop exits, 'newPlugins'
270             // will be the set of new plug-ins that should be added to the database.
271             [newPlugins removeObject:plugin];
272         }
273
274 #if !LOG_DISABLED
275         if ([newPlugins count] > 0)
276             LOG(Plugins, "New plugins:\n%@", newPlugins);
277         if ([pluginsToRemove count] > 0)
278             LOG(Plugins, "Removed plugins:\n%@", pluginsToRemove);
279 #endif
280
281         // Remove plugins from database
282         pluginEnumerator = [pluginsToRemove objectEnumerator];
283         while ((plugin = [pluginEnumerator nextObject]) != nil)
284             [self _removePlugin:plugin];
285
286         // Add new plugins to database
287         pluginEnumerator = [newPlugins objectEnumerator];
288         while ((plugin = [pluginEnumerator nextObject]) != nil)
289             [self _addPlugin:plugin];
290
291         // Build a list of MIME types.
292         NSMutableSet *MIMETypes = [[NSMutableSet alloc] init];
293         pluginEnumerator = [plugins objectEnumerator];
294         while ((plugin = [pluginEnumerator nextObject])) {
295             const PluginInfo& pluginInfo = [plugin pluginInfo];
296             for (size_t i = 0; i < pluginInfo.mimes.size(); ++i)
297                 [MIMETypes addObject:pluginInfo.mimes[i].type];
298         }
299
300         // Register plug-in views and representations.
301         NSEnumerator *MIMEEnumerator = [MIMETypes objectEnumerator];
302         NSString *MIMEType;
303         while ((MIMEType = [MIMEEnumerator nextObject]) != nil) {
304             [registeredMIMETypes addObject:MIMEType];
305
306             if ([WebView canShowMIMETypeAsHTML:MIMEType]) {
307                 // Don't allow plug-ins to override our core HTML types.
308                 continue;
309             }
310             plugin = [self pluginForMIMEType:MIMEType];
311             if ([plugin isJavaPlugIn]) {
312                 // Don't register the Java plug-in for a document view since Java files should be downloaded when not embedded.
313                 continue;
314             }
315             if ([plugin isQuickTimePlugIn] && [[WebFrameView _viewTypesAllowImageTypeOmission:NO] objectForKey:MIMEType]) {
316                 // Don't allow the QT plug-in to override any types because it claims many that we can handle ourselves.
317                 continue;
318             }
319
320             if (self == sharedDatabase)
321                 [WebView _registerPluginMIMEType:MIMEType];
322         }
323         [MIMETypes release];
324     }
325 }
326
327 - (BOOL)isMIMETypeRegistered:(NSString *)MIMEType
328 {
329     return [registeredMIMETypes containsObject:MIMEType];
330 }
331
332 - (void)addPluginInstanceView:(NSView *)view
333 {
334     [pluginInstanceViews addObject:view];
335 }
336
337 - (void)removePluginInstanceView:(NSView *)view
338 {
339     [pluginInstanceViews removeObject:view];
340 }
341
342 - (void)removePluginInstanceViewsFor:(WebFrame*)webFrame
343 {
344     // This handles handles the case where a frame or view is being destroyed and the plugin needs to be removed from the list first
345     
346     if( [pluginInstanceViews count] == 0 )
347         return;
348
349     NSView <WebDocumentView> *documentView = [[webFrame frameView] documentView]; 
350     if ([documentView isKindOfClass:[WebHTMLView class]]) {
351         for (NSView *subview in [documentView subviews]) {
352 #if ENABLE(NETSCAPE_PLUGIN_API)
353             if ([subview isKindOfClass:[WebBaseNetscapePluginView class]] || [WebPluginController isPlugInView:subview])
354 #else
355             if ([WebPluginController isPlugInView:subview])
356 #endif
357                 [pluginInstanceViews removeObject:subview]; 
358         }
359     }
360 }
361
362 - (void)destroyAllPluginInstanceViews
363 {
364     NSView *view;
365     NSArray *pli = [pluginInstanceViews allObjects];
366     NSEnumerator *enumerator = [pli objectEnumerator];
367     while ((view = [enumerator nextObject]) != nil) {
368 #if ENABLE(NETSCAPE_PLUGIN_API)
369         if ([view isKindOfClass:[WebBaseNetscapePluginView class]]) {
370             ASSERT([view respondsToSelector:@selector(stop)]);
371             [view performSelector:@selector(stop)];
372         } else
373 #endif
374         if ([WebPluginController isPlugInView:view]) {
375             ASSERT([[view superview] isKindOfClass:[WebHTMLView class]]);
376             ASSERT([[view superview] respondsToSelector:@selector(_destroyAllWebPlugins)]);
377             // this will actually destroy all plugin instances for a webHTMLView and remove them from this list
378             [[view superview] performSelector:@selector(_destroyAllWebPlugins)]; 
379         }
380     }
381 }
382     
383 @end
384
385 @implementation WebPluginDatabase (Internal)
386
387 + (NSArray *)_defaultPlugInPaths
388 {
389 #if !PLATFORM(IOS)
390     // Plug-ins are found in order of precedence.
391     // If there are duplicates, the first found plug-in is used.
392     // For example, if there is a QuickTime.plugin in the users's home directory
393     // that is used instead of the /Library/Internet Plug-ins version.
394     // The purpose is to allow non-admin users to update their plug-ins.
395     return [NSArray arrayWithObjects:
396         [NSHomeDirectory() stringByAppendingPathComponent:@"Library/Internet Plug-Ins"],
397         @"/Library/Internet Plug-Ins",
398         [[NSBundle mainBundle] builtInPlugInsPath],
399         nil];
400 #else
401     // iOS plug-ins are all located in /System/Library/Internet Plug-Ins
402 #if !PLATFORM(IOS_SIMULATOR)
403     NSArray *systemLibrary = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES);
404     if (!systemLibrary || [systemLibrary count] == 0)
405         return nil;
406     NSString *systemDir = (NSString*)[systemLibrary objectAtIndex:0];
407 #else
408     NSString* platformRootDir = [NSString stringWithUTF8String:WebKitPlatformSystemRootDirectory()];
409     NSString *systemDir = [platformRootDir stringByAppendingPathComponent:@"System/Library"];
410 #endif
411     return [NSArray arrayWithObject:[systemDir stringByAppendingPathComponent:@"Internet Plug-Ins"]];
412 #endif
413 }
414
415 - (NSArray *)_plugInPaths
416 {
417     if (self == sharedDatabase && additionalWebPlugInPaths) {
418         // Add additionalWebPlugInPaths to the global WebPluginDatabase.  We do this here for
419         // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI,
420         // which simply saved a copy of the additional paths and did not cause the plugin DB to 
421         // refresh.  See Radars 4608487 and 4609047.
422         NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease];
423         [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths];
424         return modifiedPlugInPaths;
425     } else
426         return plugInPaths;
427 }
428
429 - (void)_addPlugin:(WebBasePluginPackage *)plugin
430 {
431     ASSERT(plugin);
432     NSString *pluginPath = [plugin path];
433     ASSERT(pluginPath);
434     [plugins setObject:plugin forKey:pluginPath];
435     [plugin wasAddedToPluginDatabase:self];
436 }
437
438 - (void)_removePlugin:(WebBasePluginPackage *)plugin
439 {    
440     ASSERT(plugin);
441
442     // Unregister plug-in's MIME type registrations
443     const PluginInfo& pluginInfo = [plugin pluginInfo];
444     for (size_t i = 0; i < pluginInfo.mimes.size(); ++i) {
445         NSString *MIMEType = pluginInfo.mimes[i].type;
446
447         if ([registeredMIMETypes containsObject:MIMEType]) {
448             if (self == sharedDatabase)
449                 [WebView _unregisterPluginMIMEType:MIMEType];
450             [registeredMIMETypes removeObject:MIMEType];
451         }
452     }
453
454     // Remove plug-in from database
455     NSString *pluginPath = [plugin path];
456     ASSERT(pluginPath);
457     [plugin retain];
458     [plugins removeObjectForKey:pluginPath];
459     [plugin wasRemovedFromPluginDatabase:self];
460     [plugin release];
461 }
462
463 - (NSMutableSet *)_scanForNewPlugins
464 {
465     NSMutableSet *newPlugins = [NSMutableSet set];
466     NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator];
467     NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init];
468     NSFileManager *fileManager = [NSFileManager defaultManager];
469     NSString *pluginDirectory;
470     while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) {
471         // Get contents of each plug-in directory
472         NSEnumerator *filenameEnumerator = [[fileManager contentsOfDirectoryAtPath:pluginDirectory error:NULL] objectEnumerator];
473         NSString *filename;
474         while ((filename = [filenameEnumerator nextObject]) != nil) {
475             // Unique plug-ins by filename
476             if ([uniqueFilenames containsObject:filename])
477                 continue;
478             [uniqueFilenames addObject:filename];
479             
480             // Create a plug-in package for this path
481             NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename];
482             WebBasePluginPackage *pluginPackage = [plugins objectForKey:pluginPath];
483             if (!pluginPackage)
484                 pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath];
485             if (pluginPackage)
486                 [newPlugins addObject:pluginPackage];
487         }
488     }
489     [uniqueFilenames release];
490     
491     return newPlugins;
492 }
493
494 @end