Reviewed by Darin Adler.
authortomernic <tomernic@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Aug 2006 23:38:37 +0000 (23:38 +0000)
committertomernic <tomernic@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 15 Aug 2006 23:38:37 +0000 (23:38 +0000)
        <http://bugzilla.opendarwin.org/show_bug.cgi?id=8980>
        ASSERTION FAILED: !isLoaded (WebKit/WebKit/Plugins/WebBasePluginPackage.m:228 -[WebBasePluginPackage dealloc])

        <rdar://problem/4526052> intermittent assertion failure in -[WebBasePluginPackage dealloc] running layout tests (8980)

        * Plugins/WebPluginDatabase.h:
        * Plugins/WebPluginDatabase.m:
        (+[WebPluginDatabase installedPlugins]):
        Observe NSApplicationWillTerminateNotification so we can unload plug-ins on quit.
        (-[WebPluginDatabase plugins]):
        'plugins' is now a dictionary.
        (-[WebPluginDatabase close]):
        Call new -_removePlugin: method.
        (-[WebPluginDatabase refresh]):
        Moved parts of this method out into other methods: -_addPlugin:, -_removePlugin:, and -_scanForNewPlugins.
        (-[WebPluginDatabase _plugInPaths]):
        No changes; just moved in file.
        (-[WebPluginDatabase _addPlugin:]):
        New method.  Refactored from -refresh.  Adds a plug-in to the database.
        (-[WebPluginDatabase _removePlugin:]):
        New method.  Refactored from -refresh.  Remove a plug-in from the database.
        (-[WebPluginDatabase _scanForNewPlugins]):
        New method.  Refactored from -refresh.  Returns the list of plug-in packages on disk.
        (-[WebPluginDatabase _applicationWillTerminate]):
        New method.  Called when the application terminates.  Closes the plug-in database so that all plug-ins are
        removed from the DB (and unloaded if necessary).

        * Plugins/WebBasePluginPackage.h:
        * Plugins/WebBasePluginPackage.m:
        (-[WebBasePluginPackage initWithPath:]):
        Try to create the NSBundle first, so if the file is not a valid bundle we bail out early.  This
        avoids some stat()s and allocations during the plug-in refresh process.
        (-[WebBasePluginPackage isLoaded]):
        Removed.
        (-[WebBasePluginPackage load]):
        Base class for plug-in packages now always loads "successfully".
        (-[WebBasePluginPackage dealloc]):
        Removed this assertion.  The base plug-in package class has no concept of
        "unloading".
        (-[WebBasePluginPackage finalize]):
        ditto.
        (-[WebBasePluginPackage wasRemovedFromPluginDatabase:]):
        Moved code to unload plug-in package to WebNetscapePluginPackage.  Not all plug-in
        packages can be "unloaded".

        * Plugins/WebNetscapePluginPackage.h:
        * Plugins/WebNetscapePluginPackage.m:
        (-[WebNetscapePluginPackage _unloadWithShutdown:]):
        Combined old -unload and -unloadWithoutShutdown methods into this new one.
        (-[WebNetscapePluginPackage initWithPath:]):
        Call new unload method.
        (-[WebNetscapePluginPackage load]):
        ditto
        (-[WebNetscapePluginPackage wasRemovedFromPluginDatabase:]):
        ditto
        (-[WebNetscapePluginPackage open]):
        New method.  Called when a plug-in instance starts running.
        (-[WebNetscapePluginPackage close]):
        New method.  Called when a plug-in instance stops running.  When all plug-in instances
        close the plug-in package, and the plug-in package is removed from the database, the plug-in
        is unloaded.

        * Plugins/WebPluginPackage.m:
        (-[WebPluginPackage initWithPath:]):
        (-[WebPluginPackage load]):
        Made this a bit more efficient by checking if the bundle is already loaded.
        (-[WebBasePluginPackage unload]):
        Removed.
        (-[WebBasePluginPackage isLoaded]):
        Removed.

        * Plugins/WebBaseNetscapePluginView.m:
        (-[WebBaseNetscapePluginView start]):
        Open the plug-in package so it remains loaded while this instance uses it.
        (-[WebBaseNetscapePluginView stop]):
        Close the plug-in package when the plug-in instance is stopped.

        * Plugins/WebBaseNetscapePluginStream.m:
        (-[WebBaseNetscapePluginStream startStreamResponseURL:expectedContentLength:lastModifiedDate:MIMEType:]):
        This check is not necessary.  Netscape plug-in packages are never unloaded until all their instances have
        been stopped, and a Netscape plug-in instance will stop its streams when it is stopped.
        (-[WebBaseNetscapePluginStream _destroyStream]):
        ditto
        (-[WebBaseNetscapePluginStream finishedLoadingWithData:]):
        ditto
        (-[WebBaseNetscapePluginStream _deliverData]):
        ditto

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@15896 268f45cc-cd09-0410-ab3c-d52691b4dbfc

WebKit/ChangeLog
WebKit/Plugins/WebBaseNetscapePluginStream.m
WebKit/Plugins/WebBaseNetscapePluginView.m
WebKit/Plugins/WebBasePluginPackage.h
WebKit/Plugins/WebBasePluginPackage.m
WebKit/Plugins/WebNetscapePluginPackage.h
WebKit/Plugins/WebNetscapePluginPackage.m
WebKit/Plugins/WebPluginDatabase.h
WebKit/Plugins/WebPluginDatabase.m
WebKit/Plugins/WebPluginPackage.m

index bb596cf7d4083699713bff217b14efb9185a41d3..3be6ab303aaf8a4a65db638d1f84d68ed6a2f212 100644 (file)
@@ -1,3 +1,95 @@
+2006-08-15  Tim Omernick  <timo@apple.com>
+
+        Reviewed by Darin Adler.
+
+        <http://bugzilla.opendarwin.org/show_bug.cgi?id=8980>
+        ASSERTION FAILED: !isLoaded (WebKit/WebKit/Plugins/WebBasePluginPackage.m:228 -[WebBasePluginPackage dealloc])
+
+        <rdar://problem/4526052> intermittent assertion failure in -[WebBasePluginPackage dealloc] running layout tests (8980)
+        
+        * Plugins/WebPluginDatabase.h:
+        * Plugins/WebPluginDatabase.m:
+        (+[WebPluginDatabase installedPlugins]):
+        Observe NSApplicationWillTerminateNotification so we can unload plug-ins on quit.
+        (-[WebPluginDatabase plugins]):
+        'plugins' is now a dictionary.
+        (-[WebPluginDatabase close]):
+        Call new -_removePlugin: method.
+        (-[WebPluginDatabase refresh]):
+        Moved parts of this method out into other methods: -_addPlugin:, -_removePlugin:, and -_scanForNewPlugins.
+        (-[WebPluginDatabase _plugInPaths]):
+        No changes; just moved in file.
+        (-[WebPluginDatabase _addPlugin:]):
+        New method.  Refactored from -refresh.  Adds a plug-in to the database.
+        (-[WebPluginDatabase _removePlugin:]):
+        New method.  Refactored from -refresh.  Remove a plug-in from the database.
+        (-[WebPluginDatabase _scanForNewPlugins]):
+        New method.  Refactored from -refresh.  Returns the list of plug-in packages on disk.
+        (-[WebPluginDatabase _applicationWillTerminate]):
+        New method.  Called when the application terminates.  Closes the plug-in database so that all plug-ins are
+        removed from the DB (and unloaded if necessary).
+
+        * Plugins/WebBasePluginPackage.h:
+        * Plugins/WebBasePluginPackage.m:
+        (-[WebBasePluginPackage initWithPath:]):
+        Try to create the NSBundle first, so if the file is not a valid bundle we bail out early.  This
+        avoids some stat()s and allocations during the plug-in refresh process.
+        (-[WebBasePluginPackage isLoaded]):
+        Removed.
+        (-[WebBasePluginPackage load]):
+        Base class for plug-in packages now always loads "successfully".
+        (-[WebBasePluginPackage dealloc]):
+        Removed this assertion.  The base plug-in package class has no concept of
+        "unloading".
+        (-[WebBasePluginPackage finalize]):
+        ditto.
+        (-[WebBasePluginPackage wasRemovedFromPluginDatabase:]):
+        Moved code to unload plug-in package to WebNetscapePluginPackage.  Not all plug-in
+        packages can be "unloaded".
+
+        * Plugins/WebNetscapePluginPackage.h:
+        * Plugins/WebNetscapePluginPackage.m:
+        (-[WebNetscapePluginPackage _unloadWithShutdown:]):
+        Combined old -unload and -unloadWithoutShutdown methods into this new one.
+        (-[WebNetscapePluginPackage initWithPath:]):
+        Call new unload method.
+        (-[WebNetscapePluginPackage load]):
+        ditto
+        (-[WebNetscapePluginPackage wasRemovedFromPluginDatabase:]):
+        ditto
+        (-[WebNetscapePluginPackage open]):
+        New method.  Called when a plug-in instance starts running.
+        (-[WebNetscapePluginPackage close]):
+        New method.  Called when a plug-in instance stops running.  When all plug-in instances
+        close the plug-in package, and the plug-in package is removed from the database, the plug-in
+        is unloaded.
+
+        * Plugins/WebPluginPackage.m:
+        (-[WebPluginPackage initWithPath:]):
+        (-[WebPluginPackage load]):
+        Made this a bit more efficient by checking if the bundle is already loaded.
+        (-[WebBasePluginPackage unload]):
+        Removed.
+        (-[WebBasePluginPackage isLoaded]):
+        Removed.
+
+        * Plugins/WebBaseNetscapePluginView.m:
+        (-[WebBaseNetscapePluginView start]):
+        Open the plug-in package so it remains loaded while this instance uses it.
+        (-[WebBaseNetscapePluginView stop]):
+        Close the plug-in package when the plug-in instance is stopped.
+        
+        * Plugins/WebBaseNetscapePluginStream.m:
+        (-[WebBaseNetscapePluginStream startStreamResponseURL:expectedContentLength:lastModifiedDate:MIMEType:]):
+        This check is not necessary.  Netscape plug-in packages are never unloaded until all their instances have
+        been stopped, and a Netscape plug-in instance will stop its streams when it is stopped.
+        (-[WebBaseNetscapePluginStream _destroyStream]):
+        ditto
+        (-[WebBaseNetscapePluginStream finishedLoadingWithData:]):
+        ditto
+        (-[WebBaseNetscapePluginStream _deliverData]):
+        ditto
+
 2006-08-15  Mark Rowe  <opendarwin.org@bdash.net.nz>
 
         Reviewed by Tim H.
index 38680c86adadded5a32fb7cfd1fad8106963077d..d003d0ace2759985324c225ad549b1fd91d0cd7d 100644 (file)
@@ -200,10 +200,6 @@ static char *CarbonPathFromPOSIXPath(const char *posixPath);
 {
     ASSERT(!isTerminated);
     
-    if (![[pluginView plugin] isLoaded]) {
-        return;
-    }
-    
     [self setResponseURL:URL];
     [self setMIMEType:theMIMEType];
     
@@ -262,9 +258,8 @@ static char *CarbonPathFromPOSIXPath(const char *posixPath);
 
 - (void)_destroyStream
 {
-    if (isTerminated || ![[pluginView plugin] isLoaded]) {
+    if (isTerminated)
         return;
-    }
     
     ASSERT(reason != WEB_REASON_NONE);
     ASSERT([deliveryData length] == 0);
@@ -345,9 +340,8 @@ static char *CarbonPathFromPOSIXPath(const char *posixPath);
 
 - (void)finishedLoadingWithData:(NSData *)data
 {
-    if (![[pluginView plugin] isLoaded] || !stream.ndata) {
+    if (!stream.ndata)
         return;
-    }
     
     if ((transferMode == NP_ASFILE || transferMode == NP_ASFILEONLY) && !path) {
         path = strdup("/tmp/WebKitPlugInStreamXXXXXX");
@@ -383,9 +377,8 @@ static char *CarbonPathFromPOSIXPath(const char *posixPath);
 
 - (void)_deliverData
 {
-    if (![[pluginView plugin] isLoaded] || !stream.ndata || [deliveryData length] == 0) {
+    if (!stream.ndata || [deliveryData length] == 0)
         return;
-    }
     
     int32 totalBytes = [deliveryData length];
     int32 totalBytesDelivered = 0;
index f2d6816477717345dd09a9be0676c31aaa559538..fc2fae812026741ae5fe0c57f334d18372d05bb7 100644 (file)
@@ -1201,6 +1201,9 @@ static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEve
 
     ASSERT(NPP_New);
 
+    // Open the plug-in package so it remains loaded while this instance uses it
+    [plugin open];
+    
     // Initialize drawingModel to an invalid value so that we can detect when the plugin does not specify a drawingModel
     drawingModel = -1;
     
@@ -1226,6 +1229,7 @@ static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEve
         LOG(Plugins, "Plugin only supports QuickDraw, but QuickDraw is unavailable: %@", plugin);
         NPP_Destroy(instance, NULL);
         instance->pdata = NULL;
+        [plugin close];
         return NO;
 #endif
     }
@@ -1233,6 +1237,7 @@ static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEve
     LOG(Plugins, "NPP_New: %d", npErr);
     if (npErr != NPERR_NO_ERROR) {
         LOG_ERROR("NPP_New failed with error: %d", npErr);
+        [plugin close];
         return NO;
     }
     
@@ -1297,6 +1302,9 @@ static OSStatus TSMEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEve
 
     instance->pdata = NULL;
     
+    // This instance no longer needs the plug-in package
+    [plugin close];
+    
     // We usually remove the key event handler in resignFirstResponder but it is possible that resignFirstResponder 
     // may never get called so we can't completely rely on it.
     [self removeKeyEventHandler];
index 495a2f1d46a7c17db8ed8f3586c3f77e886daa6c..5a1ad53b8d935d036d1b24a100556616bfe522b2 100644 (file)
 {
     NSMutableSet *pluginDatabases;
     
-    BOOL isLoaded;
-    
     NSString *name;
     NSString *path;
     NSString *pluginDescription;
 
     NSBundle *bundle;
     CFBundleRef cfBundle;
-    
-    NSDate *lastModifiedDate;
 
     NSDictionary *MIMEToDescription;
     NSDictionary *MIMEToExtensions;
 - (BOOL)getPluginInfoFromPLists;
 
 - (BOOL)load;
-- (void)unload;
-- (BOOL)isLoaded;
 
 - (NSString *)name;
 - (NSString *)path;
 - (NSString *)filename;
 - (NSString *)pluginDescription;
 - (NSBundle *)bundle;
-- (NSDate *)lastModifiedDate;
 
 - (NSEnumerator *)extensionEnumerator;
 - (NSEnumerator *)MIMETypeEnumerator;
index 02f0af78470613855af9471223f10900f7fcc865..bc790ac36c0aba60b84f1859549a42594c820579 100644 (file)
 
 - (id)initWithPath:(NSString *)pluginPath
 {
-    self = [super init];
-    extensionToMIME = [[NSMutableDictionary alloc] init];
+    if (!(self = [super init]))
+        return nil;
+        
     path = [[self pathByResolvingSymlinksAndAliasesInPath:pluginPath] retain];
     bundle = [[NSBundle alloc] initWithPath:path];
+    if (!bundle) {
+        [self release];
+        return nil;
+    }
     cfBundle = CFBundleCreate(NULL, (CFURLRef)[NSURL fileURLWithPath:path]);
-    lastModifiedDate = [[[[NSFileManager defaultManager] fileAttributesAtPath:path traverseLink:YES] objectForKey:NSFileModificationDate] retain];
+    extensionToMIME = [[NSMutableDictionary alloc] init];
+    
     return self;
 }
 
     return [self getPluginInfoFromBundleAndMIMEDictionary:MIMETypes];
 }
 
-- (BOOL)isLoaded
-{
-    return isLoaded;
-}
-
 - (BOOL)load
 {
-    if (isLoaded && bundle && !BP_CreatePluginMIMETypesPreferences)
+    if (bundle && !BP_CreatePluginMIMETypesPreferences)
         BP_CreatePluginMIMETypesPreferences = (BP_CreatePluginMIMETypesPreferencesFuncPtr)CFBundleGetFunctionPointerForName(cfBundle, CFSTR("BP_CreatePluginMIMETypesPreferences"));
-    return isLoaded;
-}
-
-- (void)unload
-{
+    
+    return YES;
 }
 
 - (void)dealloc
 {
-    ASSERT(!isLoaded);
-    
     ASSERT(!pluginDatabases || [pluginDatabases count] == 0);
     [pluginDatabases release];
     
     [bundle release];
     if (cfBundle)
         CFRelease(cfBundle);
-
-    [lastModifiedDate release];
     
     [super dealloc];
 }
 
 - (void)finalize
 {
-    ASSERT(!isLoaded);
-
     ASSERT(!pluginDatabases || [pluginDatabases count] == 0);
     [pluginDatabases release];
 
     return bundle;
 }
 
-- (NSDate *)lastModifiedDate
-{
-    return lastModifiedDate;
-}
-
 - (void)setName:(NSString *)theName
 {
     [name release];
         name, path, [MIMEToExtensions description], [MIMEToDescription description], pluginDescription];
 }
 
-- (BOOL)isEqual:(id)object
-{
-    return ([object isKindOfClass:[WebBasePluginPackage class]] &&
-            [[object name] isEqualToString:name] &&
-            [[object lastModifiedDate] isEqual:lastModifiedDate]);
-}
-
-- (WebNSUInteger)hash
-{
-    return [[name stringByAppendingString:[lastModifiedDate description]] hash];
-}
-
 - (BOOL)isQuickTimePlugIn
 {
     NSString *bundleIdentifier = [[self bundle] bundleIdentifier];
     ASSERT([pluginDatabases containsObject:database]);
 
     [pluginDatabases removeObject:database];
-
-    if ([pluginDatabases count] == 0)
-        [self unload];
 }
 
 @end
index 85be8f8251607397a9f3dfa14bb6b4e0a391aaef..aa435278394b6eee704a7d54781d96320846ee76 100644 (file)
@@ -66,8 +66,17 @@ typedef enum {
     NPP_SetValueProcPtr NPP_SetValue;
     NPP_ShutdownProcPtr NPP_Shutdown;
     NPP_GetJavaClassProcPtr NPP_GetJavaClass;
+
+    BOOL isLoaded;
+    BOOL needsUnload;
+    unsigned int instanceCount;
 }
 
+// Netscape plug-in packages must be explicitly opened and closed by each plug-in instance.
+// This is to protect Netscape plug-ins from being unloaded while they are in use.
+- (void)open;
+- (void)close;
+
 - (WebExecutableType)executableType;
 
 - (NPP_NewProcPtr)NPP_New;
index 635800b14c005668429caea91f13f4ac9d338e9f..a022fa43ea53feddba1b8e650288912ce6adf8ae 100644 (file)
@@ -48,6 +48,10 @@ static TransitionVector tVectorForFunctionPointer(FunctionPointer);
 #define RealPlayerAppIndentifier                @"com.RealNetworks.RealOne Player"
 #define RealPlayerPluginFilename                @"RealPlayer Plugin"
 
+@interface WebNetscapePluginPackage (Internal)
+- (void)_unloadWithShutdown:(BOOL)shutdown;
+@end
+
 @implementation WebNetscapePluginPackage
 
 #ifndef __LP64__
@@ -233,7 +237,7 @@ static TransitionVector tVectorForFunctionPointer(FunctionPointer);
     // Initializing a plugin package can cause it to be loaded.  If there was an error initializing the plugin package,
     // ensure that it is unloaded before deallocating it (WebBasePluginPackage requires & asserts this).
     if (![self _initWithPath:pluginPath]) {
-        [self unload];
+        [self _unloadWithShutdown:YES];
         [self release];
         return nil;
     }
@@ -249,32 +253,6 @@ static TransitionVector tVectorForFunctionPointer(FunctionPointer);
         return WebMachOExecutableType;
 }
 
-- (BOOL)isLoaded
-{
-    return isLoaded;
-}
-
-- (void)unloadWithoutShutdown
-{
-    if (!isLoaded)
-        return;
-
-    if (resourceRef != -1)
-        [self closeResourceFile:resourceRef];
-
-    if (isBundle)
-        CFBundleUnloadExecutable(cfBundle);
-    else
-#ifndef __LP64__
-        // CFM is not supported in 64-bit
-        WebCloseConnection(&connID);
-#endif
-
-    LOG(Plugins, "Plugin Unloaded");
-    isLoaded = NO;
-}
-
-
 - (void)launchRealPlayer
 {
     CFURLRef appURL = NULL;
@@ -562,21 +540,9 @@ static TransitionVector tVectorForFunctionPointer(FunctionPointer);
     return [super load];
 
 abort:
-    [self unloadWithoutShutdown];
+    [self _unloadWithShutdown:NO];
     return NO;
 }
-    
-- (void)unload
-{
-    if (!isLoaded)
-        return;
-    
-    LOG(Plugins, "Unloading %@...", name);
-
-    NPP_Shutdown();
-
-    [self unloadWithoutShutdown];
-}
 
 - (NPP_SetWindowProcPtr)NPP_SetWindow
 {
@@ -641,6 +607,39 @@ abort:
     return NPP_Print;
 }
 
+- (void)wasRemovedFromPluginDatabase:(WebPluginDatabase *)database
+{
+    [super wasRemovedFromPluginDatabase:database];
+    
+    // Unload when removed from final plug-in database
+    if ([pluginDatabases count] == 0)
+        [self _unloadWithShutdown:YES];
+}
+
+- (void)open
+{
+    instanceCount++;
+    
+    // Handle the case where all instances close a plug-in package, but another
+    // instance opens the package before it is unloaded (which only happens when
+    // the plug-in database is refreshed)
+    needsUnload = NO;
+    
+    if (!isLoaded) {
+        // Should load when the first instance opens the plug-in package
+        ASSERT(instanceCount == 1);
+        [self load];
+    }
+}
+
+- (void)close
+{
+    ASSERT(instanceCount > 0);
+    instanceCount--;
+    if (instanceCount == 0 && needsUnload)
+        [self _unloadWithShutdown:YES];
+}
+
 @end
 
 
@@ -677,3 +676,38 @@ TransitionVector tVectorForFunctionPointer(FunctionPointer fp)
     }
     return (TransitionVector)newGlue;
 }
+
+@implementation WebNetscapePluginPackage (Internal)
+
+- (void)_unloadWithShutdown:(BOOL)shutdown
+{
+    if (!isLoaded)
+        return;
+    
+    LOG(Plugins, "Unloading %@...", name);
+
+    // Cannot unload a plug-in package while an instance is still using it
+    if (instanceCount > 0) {
+        needsUnload = YES;
+        return;
+    }
+
+    if (shutdown && NPP_Shutdown)
+        NPP_Shutdown();
+
+    if (resourceRef != -1)
+        [self closeResourceFile:resourceRef];
+
+    if (isBundle)
+        CFBundleUnloadExecutable(cfBundle);
+    else
+#ifndef __LP64__
+        // CFM is not supported in 64-bit
+        WebCloseConnection(&connID);
+#endif
+
+    LOG(Plugins, "Plugin Unloaded");
+    isLoaded = NO;
+}
+
+@end
index a10158548d9fd9c09986c0c762ddf04933f46640..4ffb12d346f7372a3ffd3610656f2d548ab4fbb8 100644 (file)
@@ -32,7 +32,7 @@
 
 @interface WebPluginDatabase : NSObject
 {
-    NSMutableSet *plugins;
+    NSMutableDictionary *plugins;
     NSMutableSet *registeredMIMETypes;
     NSArray *plugInPaths;
 }
index cb071da386147046280ac9ed373d586a9ce73e05..2b2fdae0538895b7e520fc71351b563239897dd5 100644 (file)
 
 @interface WebPluginDatabase (Internal)
 + (NSArray *)_defaultPlugInPaths;
+- (NSArray *)_plugInPaths;
+- (void)_addPlugin:(WebBasePluginPackage *)plugin;
+- (void)_removePlugin:(WebBasePluginPackage *)plugin;
+- (NSMutableSet *)_scanForNewPlugins;
+- (void)_applicationWillTerminate;
 @end
 
 @implementation WebPluginDatabase
@@ -55,6 +60,12 @@ static WebPluginDatabase *database = nil;
         database = [[WebPluginDatabase alloc] init];
         [database setPlugInPaths:[self _defaultPlugInPaths]];
         [database refresh];
+        
+        // Clear the global plug-in database on app exit
+        [[NSNotificationCenter defaultCenter] addObserver:database
+                                                 selector:@selector(_applicationWillTerminate)
+                                                     name:NSApplicationWillTerminateNotification
+                                                   object:NSApp];
     }
     
     return database;
@@ -128,7 +139,7 @@ static WebPluginDatabase *database = nil;
 
 - (NSArray *)plugins
 {
-    return [plugins allObjects];
+    return [plugins allValues];
 }
 
 static NSArray *additionalWebPlugInPaths;
@@ -159,7 +170,10 @@ static NSArray *additionalWebPlugInPaths;
 
 - (void)close
 {
-    [plugins makeObjectsPerformSelector:@selector(wasRemovedFromPluginDatabase:) withObject:self];
+    NSEnumerator *pluginEnumerator = [[self plugins] objectEnumerator];
+    WebBasePluginPackage *plugin;
+    while ((plugin = [pluginEnumerator nextObject]) != nil)
+        [self _removePlugin:plugin];
     [plugins release];
     plugins = nil;
 }
@@ -183,100 +197,59 @@ static NSArray *additionalWebPlugInPaths;
     [super dealloc];
 }
 
-- (NSArray *)_plugInPaths
-{
-    if (self == database && additionalWebPlugInPaths) {
-        // Add additionalWebPlugInPaths to the global WebPluginDatabase.  We do this here for
-        // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI,
-        // which simply saved a copy of the additional paths and did not cause the plugin DB to 
-        // refresh.  See Radars 4608487 and 4609047.
-        NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease];
-        [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths];
-        return modifiedPlugInPaths;
-    } else
-        return plugInPaths;
-}
-
 - (void)refresh
 {
-    NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator];
-    NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init];
-    NSFileManager *fileManager = [NSFileManager defaultManager];
-    NSMutableSet *newPlugins = [[NSMutableSet alloc] init];
-    NSString *pluginDirectory;
+    // This method does a bit of autoreleasing, so create an autorelease pool to ensure that calling
+    // -refresh multiple times does not bloat the default pool.
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
     
-    // Create a new set of plug-ins.
-    while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) {
-        NSEnumerator *filenameEnumerator = [[fileManager directoryContentsAtPath:pluginDirectory] objectEnumerator];
-        NSString *filename;
-        while ((filename = [filenameEnumerator nextObject]) != nil) {
-            if (![uniqueFilenames containsObject:filename]) {
-                [uniqueFilenames addObject:filename];
-                NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename];
-                WebBasePluginPackage *pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath];
-                if (pluginPackage) {
-                    [newPlugins addObject:pluginPackage];
-                }
-            }
-        }
+    // Create map from plug-in path to WebBasePluginPackage
+    if (!plugins)
+        plugins = [[NSMutableDictionary alloc] initWithCapacity:12];
+
+    // Find all plug-ins on disk
+    NSMutableSet *newPlugins = [self _scanForNewPlugins];
+
+    // Find plug-ins to remove from database (i.e., plug-ins that no longer exist on disk)
+    NSMutableSet *pluginsToRemove = [NSMutableSet set];
+    NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
+    WebBasePluginPackage *plugin;
+    while ((plugin = [pluginEnumerator nextObject]) != nil) {
+        // Any plug-ins that were removed from disk since the last refresh should be removed from
+        // the database.
+        if (![newPlugins containsObject:plugin])
+            [pluginsToRemove addObject:plugin];
+            
+        // Remove every member of 'plugins' from 'newPlugins'.  After this loop exits, 'newPlugins'
+        // will be the set of new plug-ins that should be added to the database.
+        [newPlugins removeObject:plugin];
     }
-    
-    [uniqueFilenames release];
-    
-    //  Remove all uninstalled plug-ins and add the new plug-ins.
-    if (plugins) {
-        // Unregister plug-in views and representations
-        NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
-        WebBasePluginPackage *pluginPackage;
-        while ((pluginPackage = [pluginEnumerator nextObject])) {
-            NSEnumerator *MIMETypeEnumerator = [pluginPackage MIMETypeEnumerator];
-            NSString *MIMEType;
-            while ((MIMEType = [MIMETypeEnumerator nextObject])) {
-                if ([registeredMIMETypes containsObject:MIMEType]) {
-                    if (self == database)
-                        [WebView _unregisterViewClassAndRepresentationClassForMIMEType:MIMEType];
-                    [registeredMIMETypes removeObject:MIMEType];
-                }
-            }
-        }
-        
-        NSMutableSet *pluginsToUnload = [plugins mutableCopy];
-        [pluginsToUnload minusSet:newPlugins];
-        [newPlugins minusSet:plugins];
+
 #if !LOG_DISABLED
-        if ([newPlugins count] > 0) {
-            LOG(Plugins, "New plugins:\n%@", newPlugins);
-        }
-        if ([pluginsToUnload count] > 0) {
-            LOG(Plugins, "Removed plugins:\n%@", pluginsToUnload);
-        }
+    if ([newPlugins count] > 0)
+        LOG(Plugins, "New plugins:\n%@", newPlugins);
+    if ([pluginsToRemove count] > 0)
+        LOG(Plugins, "Removed plugins:\n%@", pluginsToRemove);
 #endif
-        // Unload plugins
-        [pluginsToUnload makeObjectsPerformSelector:@selector(wasRemovedFromPluginDatabase:) withObject:self];
-        [plugins minusSet:pluginsToUnload];
-        [pluginsToUnload release];
-
-        // Add new plugins
-        [plugins unionSet:newPlugins];
-        [newPlugins makeObjectsPerformSelector:@selector(wasAddedToPluginDatabase:) withObject:self];
-        [newPlugins release];
-    } else {
-        LOG(Plugins, "Plugin database initialization:\n%@", newPlugins);
-        plugins = newPlugins;
-        [newPlugins makeObjectsPerformSelector:@selector(wasAddedToPluginDatabase:) withObject:self];
-    }
+
+    // Remove plugins from database
+    pluginEnumerator = [pluginsToRemove objectEnumerator];
+    while ((plugin = [pluginEnumerator nextObject]) != nil) 
+        [self _removePlugin:plugin];
     
+    // Add new plugins to database
+    pluginEnumerator = [newPlugins objectEnumerator];
+    while ((plugin = [pluginEnumerator nextObject]) != nil)
+        [self _addPlugin:plugin];
+
     // Build a list of MIME types.
     NSMutableSet *MIMETypes = [[NSMutableSet alloc] init];
-    NSEnumerator *pluginEnumerator = [plugins objectEnumerator];
-    WebBasePluginPackage *plugin;
-    while ((plugin = [pluginEnumerator nextObject]) != nil) {
+    pluginEnumerator = [plugins objectEnumerator];
+    while ((plugin = [pluginEnumerator nextObject]) != nil)
         [MIMETypes addObjectsFromArray:[[plugin MIMETypeEnumerator] allObjects]];
-    }
     
     // Register plug-in views and representations.
-    NSEnumerator *MIMEEnumerator = [[MIMETypes allObjects] objectEnumerator];
-    [MIMETypes release];
+    NSEnumerator *MIMEEnumerator = [MIMETypes objectEnumerator];
     NSString *MIMEType;
     while ((MIMEType = [MIMEEnumerator nextObject]) != nil) {
         if ([WebView canShowMIMETypeAsHTML:MIMEType])
@@ -294,6 +267,9 @@ static NSArray *additionalWebPlugInPaths;
             [WebView registerViewClass:[WebHTMLView class] representationClass:[WebHTMLRepresentation class] forMIMEType:MIMEType];
         [registeredMIMETypes addObject:MIMEType];
     }
+    [MIMETypes release];
+    
+    [pool release];
 }
 
 - (BOOL)isMIMETypeRegistered:(NSString *)MIMEType
@@ -319,4 +295,91 @@ static NSArray *additionalWebPlugInPaths;
         nil];
 }
 
+- (NSArray *)_plugInPaths
+{
+    if (self == database && additionalWebPlugInPaths) {
+        // Add additionalWebPlugInPaths to the global WebPluginDatabase.  We do this here for
+        // backward compatibility with earlier versions of the +setAdditionalWebPlugInPaths: SPI,
+        // which simply saved a copy of the additional paths and did not cause the plugin DB to 
+        // refresh.  See Radars 4608487 and 4609047.
+        NSMutableArray *modifiedPlugInPaths = [[plugInPaths mutableCopy] autorelease];
+        [modifiedPlugInPaths addObjectsFromArray:additionalWebPlugInPaths];
+        return modifiedPlugInPaths;
+    } else
+        return plugInPaths;
+}
+
+- (void)_addPlugin:(WebBasePluginPackage *)plugin
+{
+    ASSERT(plugin);
+    NSString *pluginPath = [plugin path];
+    ASSERT(pluginPath);
+    [plugins setObject:plugin forKey:pluginPath];
+    [plugin wasAddedToPluginDatabase:self];
+}
+
+- (void)_removePlugin:(WebBasePluginPackage *)plugin
+{    
+    ASSERT(plugin);
+
+    // Unregister plug-in's MIME type registrations
+    NSEnumerator *MIMETypeEnumerator = [plugin MIMETypeEnumerator];
+    NSString *MIMEType;
+    while ((MIMEType = [MIMETypeEnumerator nextObject])) {
+        if ([registeredMIMETypes containsObject:MIMEType]) {
+            if (self == database)
+                [WebView _unregisterViewClassAndRepresentationClassForMIMEType:MIMEType];
+            [registeredMIMETypes removeObject:MIMEType];
+        }
+    }
+
+    // Remove plug-in from database
+    NSString *pluginPath = [plugin path];
+    ASSERT(pluginPath);
+    [plugin retain];
+    [plugins removeObjectForKey:pluginPath];
+    [plugin wasRemovedFromPluginDatabase:self];
+    [plugin release];
+}
+
+- (NSMutableSet *)_scanForNewPlugins
+{
+    NSMutableSet *newPlugins = [[[NSMutableSet alloc] init] autorelease];
+    NSEnumerator *directoryEnumerator = [[self _plugInPaths] objectEnumerator];
+    NSMutableSet *uniqueFilenames = [[NSMutableSet alloc] init];
+    NSFileManager *fileManager = [NSFileManager defaultManager];
+    NSString *pluginDirectory;
+    while ((pluginDirectory = [directoryEnumerator nextObject]) != nil) {
+        // Get contents of each plug-in directory
+        NSEnumerator *filenameEnumerator = [[fileManager directoryContentsAtPath:pluginDirectory] objectEnumerator];
+        NSString *filename;
+        while ((filename = [filenameEnumerator nextObject]) != nil) {
+            // Unique plug-ins by filename
+            if ([uniqueFilenames containsObject:filename])
+                continue;
+            [uniqueFilenames addObject:filename];
+            
+            // Create a plug-in package for this path
+            NSString *pluginPath = [pluginDirectory stringByAppendingPathComponent:filename];
+            WebBasePluginPackage *pluginPackage = [plugins objectForKey:pluginPath];
+            if (!pluginPackage)
+                pluginPackage = [WebBasePluginPackage pluginWithPath:pluginPath];
+            if (pluginPackage)
+                [newPlugins addObject:pluginPackage];
+        }
+    }
+    [uniqueFilenames release];
+    
+    return newPlugins;
+}
+
+- (void)_applicationWillTerminate
+{
+    ASSERT(self == database);
+    // Remove all plug-ins from database.  Netscape plug-ins have "destructor functions" that should be called
+    // when the browser unloads the plug-in.  These functions can do important things, such as closing/deleting files,
+    // so it is important to ensure that they are properly called when the application terminates.
+    [self close];
+}
+
 @end
index 1adf9f4f84fc96f6be0561c35970585f047b0773..d7f144eb4a69383241acb1ef5405ab8c256b0897 100644 (file)
@@ -42,7 +42,8 @@ NSString *WebPlugInContainingElementKey =       @"WebPlugInContainingElementKey"
 
 - initWithPath:(NSString *)pluginPath
 {
-    [super initWithPath:pluginPath];
+    if (!(self = [super initWithPath:pluginPath]))
+        return nil;
 
     if (bundle == nil) {
         [self release];
@@ -81,20 +82,16 @@ NSString *WebPlugInContainingElementKey =       @"WebPlugInContainingElementKey"
 
 - (BOOL)load
 {
-    if (isLoaded) {
-        return YES;
-    }
-    
 #if !LOG_DISABLED
     CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
 #endif
     
-    [bundle principalClass];
-    isLoaded = [bundle isLoaded];
-    if (!isLoaded) {
-        return NO;
+    // Load the bundle
+    if (![bundle isLoaded]) {
+        if (![bundle load])
+            return NO;
     }
-
+    
 #if !LOG_DISABLED
     CFAbsoluteTime duration = CFAbsoluteTimeGetCurrent() - start;
     LOG(Plugins, "principalClass took %f seconds for: %@", duration, [self name]);
@@ -102,16 +99,6 @@ NSString *WebPlugInContainingElementKey =       @"WebPlugInContainingElementKey"
     return [super load];
 }
 
-- (void)unload
-{
-    isLoaded = NO;
-}
-
-- (BOOL)isLoaded
-{
-    return [bundle isLoaded];
-}
-
 @end
 
 @implementation NSObject (WebScripting)