Fix GrowlMail's non-summary mode to not ask for the message body's attributed string on a secondary thread. This fixes an occasional crash with Safari 3, and a 100%-of-the-time crash with Safari 4 beta.
authorPeter Hosey
Wed Feb 25 01:02:44 2009 -0800 (2009-02-25)
changeset 417265a0bcf23292
parent 4171 2918a61fe9b3
child 4173 e66ac7c1210d
Fix GrowlMail's non-summary mode to not ask for the message body's attributed string on a secondary thread. This fixes an occasional crash with Safari 3, and a 100%-of-the-time crash with Safari 4 beta.

The fix is simply to only retrieve the message body object on a secondary thread, and then once we have it (or have given up on getting it), perform the rest of the job on the main thread.
Extras/GrowlMail/GrowlMail.m
Extras/GrowlMail/Message+GrowlMail.h
Extras/GrowlMail/Message+GrowlMail.m
     1.1 --- a/Extras/GrowlMail/GrowlMail.m	Wed Feb 25 01:02:42 2009 -0800
     1.2 +++ b/Extras/GrowlMail/GrowlMail.m	Wed Feb 25 01:02:44 2009 -0800
     1.3 @@ -283,7 +283,7 @@
     1.4  		 * We're making some assumptions about Mail's internals, but the fact that notifications are posted on auxiliary threads
     1.5  		 * and then again with a _inMainThread_ suffix on the main thread indicates that threads are being used for mail access elsewhere.
     1.6  		 */
     1.7 -		[NSThread detachNewThreadSelector:@selector(GMShowNotification)
     1.8 +		[NSThread detachNewThreadSelector:@selector(GMShowNotificationPart1)
     1.9  								 toTarget:message
    1.10  							   withObject:nil];
    1.11  	} else {
     2.1 --- a/Extras/GrowlMail/Message+GrowlMail.h	Wed Feb 25 01:02:42 2009 -0800
     2.2 +++ b/Extras/GrowlMail/Message+GrowlMail.h	Wed Feb 25 01:02:44 2009 -0800
     2.3 @@ -36,9 +36,19 @@
     2.4  
     2.5  @interface Message(GrowlMail)
     2.6  /*!
     2.7 - * @brief Show a Growl notification for this message
     2.8 + * @brief Begin to show a Growl notification for this message
     2.9   *
    2.10   * This should be called on an auxiliary thread as it may block.
    2.11 + *
    2.12 + * This method calls part 2 on the main thread; you do not need to call part 2 yourself.
    2.13   */
    2.14 -- (void) GMShowNotification;
    2.15 +- (void) GMShowNotificationPart1;
    2.16 +/*!
    2.17 + * @brief Finish showing a Growl notification for this message
    2.18 + *
    2.19 + * Don't call this directly - call part 1 instead. Part 1 will call this on the main thread because it will get the attributed string for the message body, which may require going through WebKit (which will throw an exception if we call it on a secondary thread).
    2.20 + *
    2.21 + *	@param[in]	messageBody	The object containing the body of the message, which part 1 obtained.
    2.22 + */
    2.23 +- (void) GMShowNotificationPart2:(MessageBody *)messageBody;
    2.24  @end
     3.1 --- a/Extras/GrowlMail/Message+GrowlMail.m	Wed Feb 25 01:02:42 2009 -0800
     3.2 +++ b/Extras/GrowlMail/Message+GrowlMail.m	Wed Feb 25 01:02:44 2009 -0800
     3.3 @@ -52,22 +52,18 @@
     3.4  
     3.5  @implementation Message (GrowlMail)
     3.6  
     3.7 -- (void) GMShowNotification
     3.8 -{
     3.9 +- (void) GMShowNotificationPart1 {
    3.10  	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    3.11  
    3.12 -	NSString *account = (NSString *)[[[self mailbox] account] displayName];
    3.13 -	NSString *sender = [self sender];
    3.14 -	NSString *senderAddress = [sender uncommentedAddress];
    3.15 -	NSString *subject = (NSString *)[self subject];
    3.16 -	NSString *body;
    3.17 +	MessageBody *messageBody = nil;
    3.18 +
    3.19  	NSString *titleFormat = (NSString *)GMTitleFormatString();
    3.20  	NSString *descriptionFormat = (NSString *)GMDescriptionFormatString();
    3.21  
    3.22  	if ([titleFormat rangeOfString:@"%body"].location != NSNotFound ||
    3.23  			[descriptionFormat rangeOfString:@"%body"].location != NSNotFound) {
    3.24  		/* We will need the body */
    3.25 -		MessageBody *messageBody = [self messageBodyIfAvailable];
    3.26 +		messageBody = [self messageBodyIfAvailable];
    3.27  		int nonBlockingAttempts = 0;
    3.28  		while (!messageBody && nonBlockingAttempts < 3) {
    3.29  			/* No message body available yet, but we need one */
    3.30 @@ -80,36 +76,23 @@
    3.31  
    3.32  		/* Already tried three times (3 seconds); this time, block this thread to get it. */ 
    3.33  		if (!messageBody) messageBody = [self messageBody];
    3.34 -
    3.35 -		if (messageBody) {
    3.36 -			NSString *originalBody = nil;
    3.37 -			/* stringForIndexing selector: Mail.app 3.0 in OS X 10.4, not in 10.5. */
    3.38 -			if ([messageBody respondsToSelector:@selector(stringForIndexing)])
    3.39 -				originalBody = [messageBody stringForIndexing];
    3.40 -			else if ([messageBody respondsToSelector:@selector(attributedString)])
    3.41 -				originalBody = [[messageBody attributedString] string];
    3.42 -			else if ([messageBody respondsToSelector:@selector(stringValueForJunkEvaluation:)])
    3.43 -				originalBody = [messageBody stringValueForJunkEvaluation:NO];
    3.44 -			if (originalBody) {
    3.45 -				NSMutableString *transformedBody = [[[originalBody stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy] autorelease];
    3.46 -				unsigned lengthWithoutWhitespace = [transformedBody length];
    3.47 -				[transformedBody trimStringToFirstNLines:4U];
    3.48 -				unsigned length = [transformedBody length];
    3.49 -				if (length > 200U) {
    3.50 -					[transformedBody deleteCharactersInRange:NSMakeRange(200U, length - 200U)];
    3.51 -					length = 200U;
    3.52 -				}
    3.53 -				if (length != lengthWithoutWhitespace)
    3.54 -					[transformedBody appendString:[NSString stringWithUTF8String:"\xE2\x80\xA6"]];
    3.55 -				body = (NSString *)transformedBody;
    3.56 -			} else {
    3.57 -				body = @"";	
    3.58 -			}
    3.59 -		} else {
    3.60 -			body = @"";
    3.61 -		}
    3.62 -	} else
    3.63 -		body = @"";
    3.64 +	}
    3.65 +
    3.66 +	[self performSelectorOnMainThread:@selector(GMShowNotificationPart2:)
    3.67 +						   withObject:messageBody
    3.68 +						waitUntilDone:NO];
    3.69 +
    3.70 +	[pool release];
    3.71 +}
    3.72 +
    3.73 +- (void) GMShowNotificationPart2:(MessageBody *)messageBody {
    3.74 +	NSString *account = (NSString *)[[[self mailbox] account] displayName];
    3.75 +	NSString *sender = [self sender];
    3.76 +	NSString *senderAddress = [sender uncommentedAddress];
    3.77 +	NSString *subject = (NSString *)[self subject];
    3.78 +	NSString *body;
    3.79 +	NSString *titleFormat = (NSString *)GMTitleFormatString();
    3.80 +	NSString *descriptionFormat = (NSString *)GMDescriptionFormatString();
    3.81  
    3.82  	/* The fullName selector is not available in Mail.app 2.0. */
    3.83  	if ([sender respondsToSelector:@selector(fullName)])
    3.84 @@ -117,6 +100,34 @@
    3.85  	else if ([sender addressComment])
    3.86  		sender = [sender addressComment];
    3.87  
    3.88 +	if (messageBody) {
    3.89 +		NSString *originalBody = nil;
    3.90 +		/* stringForIndexing selector: Mail.app 3.0 in OS X 10.4, not in 10.5. */
    3.91 +		if ([messageBody respondsToSelector:@selector(stringForIndexing)])
    3.92 +			originalBody = [messageBody stringForIndexing];
    3.93 +		else if ([messageBody respondsToSelector:@selector(attributedString)])
    3.94 +			originalBody = [[messageBody attributedString] string];
    3.95 +		else if ([messageBody respondsToSelector:@selector(stringValueForJunkEvaluation:)])
    3.96 +			originalBody = [messageBody stringValueForJunkEvaluation:NO];
    3.97 +		if (originalBody) {
    3.98 +			NSMutableString *transformedBody = [[[originalBody stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy] autorelease];
    3.99 +			unsigned lengthWithoutWhitespace = [transformedBody length];
   3.100 +			[transformedBody trimStringToFirstNLines:4U];
   3.101 +			unsigned length = [transformedBody length];
   3.102 +			if (length > 200U) {
   3.103 +				[transformedBody deleteCharactersInRange:NSMakeRange(200U, length - 200U)];
   3.104 +				length = 200U;
   3.105 +			}
   3.106 +			if (length != lengthWithoutWhitespace)
   3.107 +				[transformedBody appendString:[NSString stringWithUTF8String:"\xE2\x80\xA6"]];
   3.108 +			body = (NSString *)transformedBody;
   3.109 +		} else {
   3.110 +			body = @"";	
   3.111 +		}
   3.112 +	} else {
   3.113 +		body = @"";
   3.114 +	}
   3.115 +
   3.116  	NSArray *keywords = [NSArray arrayWithObjects:
   3.117  		@"%sender",
   3.118  		@"%subject",
   3.119 @@ -186,8 +197,6 @@
   3.120  							   clickContext:clickContext];	// non-nil click context
   3.121  
   3.122  	[GrowlMail didFinishNotificationForMessage:self];
   3.123 -
   3.124 -	[pool release];
   3.125  }
   3.126  
   3.127  @end