Bug 308552 Growl Integration for Mail Alerts on Mac OS X. Patch by David Humphrey, bitrot updates by Standard8. r=Mnyromyr,sr=bienvenu
authorDavid Humphrey <david.humphrey@senecac.on.ca>
Sat, 11 Oct 2008 09:01:09 +0100
changeset 585 a2226c07cc4da8205cd04f6b6b6bc271dffad9c0
parent 584 773da60a611342b6fa44dfe6812b2a3b3bb31cb6
child 586 1f39876064102605080798473fc6b238681ab755
push idunknown
push userunknown
push dateunknown
reviewersMnyromyr, bienvenu
bugs308552
Bug 308552 Growl Integration for Mail Alerts on Mac OS X. Patch by David Humphrey, bitrot updates by Standard8. r=Mnyromyr,sr=bienvenu
mail/locales/en-US/chrome/messenger/messenger.properties
mailnews/base/build/Makefile.in
mailnews/base/src/nsMessengerOSXIntegration.cpp
mailnews/base/src/nsMessengerOSXIntegration.h
mailnews/build/Makefile.in
suite/locales/en-US/chrome/mailnews/messenger.properties
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -447,16 +447,17 @@ copyToFolderAgain=Copy to "%1$S" Again
 copyToFolderAgainAccessKey=t
 
 #LOCALIZATION NOTE %1$S is the e-mail address of the person we will allow remote images for
 alwaysLoadRemoteContentForSender1= Always load remote images from %1$S 
 
 # Strings for growl notifications on Mac OS X
 subjectNotificationTitle=Subject: "%1$S"
 senderNotificationText=Sender: "%1$S"
+growlNotification=New Mail
 
 # mailCommands.js
 emptyJunkTitle=Confirm
 emptyJunkMessage=Are you sure you want to permanently delete all messages and subfolders in the Junk folder?
 emptyJunkDontAsk=Don't ask me again.
 emptyTrashTitle=Confirm
 emptyTrashMessage=Are you sure you want to permanently delete all messages and subfolders in the Trash folder?
 emptyTrashDontAsk=Don't ask me again.
--- a/mailnews/base/build/Makefile.in
+++ b/mailnews/base/build/Makefile.in
@@ -79,16 +79,20 @@ REQUIRES	= xpcom \
 		  appcomps \
 		  toolkitcomps \
 		  msgnews \
 		  msgimap \
 		  gfx \
 		  webbrwsr \
 		  $(NULL)
 
+ifeq ($(OS_TARGET),Darwin)
+REQUIRES += alerts
+endif
+
 CPPSRCS		= nsMsgFactory.cpp
 
 SHARED_LIBRARY_LIBS = \
 		../src/$(LIB_PREFIX)msgbase_s.$(LIB_SUFFIX) \
 		../search/src/$(LIB_PREFIX)msgsearch_s.$(LIB_SUFFIX) \
 		$(NULL)
 
 ifeq ($(USE_SHORT_LIBNAME),1)
--- a/mailnews/base/src/nsMessengerOSXIntegration.cpp
+++ b/mailnews/base/src/nsMessengerOSXIntegration.cpp
@@ -59,19 +59,63 @@
 #include "nsIBaseWindow.h"
 #include "nsIWidget.h"
 #include "nsIObserverService.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h" 
 #include "nsIMessengerWindowService.h"
 #include "prprf.h"
 #include "nsIWeakReference.h"
+#include "nsIAlertsService.h"
+#include "nsIStringBundle.h"
+#include "nsToolkitCompsCID.h"
+#include "nsINotificationsList.h"
 
 #include <Carbon/Carbon.h>
 
+#define kNewMailAlertIcon "chrome://messenger/skin/icons/new-mail-alert.png"
+#define kBiffShowAlertPref "mail.biff.show_alert"
+
+static void openMailWindow(const nsACString& aFolderUri)
+{
+  nsresult rv;
+  nsCOMPtr<nsIMsgMailSession> mailSession ( do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv));
+  if (NS_FAILED(rv))
+    return;
+
+  nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+  rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+  if (topMostMsgWindow)
+  {
+    if (!aFolderUri.IsEmpty())
+    {
+      nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+      topMostMsgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+      if (windowCommands)
+        windowCommands->SelectFolder(aFolderUri);
+    }
+    
+    nsCOMPtr<nsIDOMWindowInternal> domWindow;
+    topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+    domWindow->Focus();
+  }
+  else
+  {
+    // the user doesn't have a mail window open already so open one for them...
+    nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
+      do_GetService(NS_MESSENGERWINDOWSERVICE_CONTRACTID);
+    // if we want to preselect the first account with new mail,
+    // here is where we would try to generate a uri to pass in
+    // (and add code to the messenger window service to make that work)
+    if (messengerWindowService)
+      messengerWindowService->OpenMessengerWindowWithUri(
+                                "mail:3pane", nsCString(aFolderUri).get(), nsMsgKey_None);
+  }
+}
+
 nsMessengerOSXIntegration::nsMessengerOSXIntegration()
 {
   mBiffStateAtom = do_GetAtom("BiffState");
 
   mBiffIconVisible = PR_FALSE;
   mSuppressBiffIcon = PR_FALSE;
   mAlertInProgress = PR_FALSE;
   NS_NewISupportsArray(getter_AddRefs(mFoldersWithNewMail));
@@ -88,40 +132,42 @@ nsMessengerOSXIntegration::~nsMessengerO
 
 NS_IMPL_ADDREF(nsMessengerOSXIntegration)
 NS_IMPL_RELEASE(nsMessengerOSXIntegration)
 
 NS_INTERFACE_MAP_BEGIN(nsMessengerOSXIntegration)
    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
    NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
    NS_INTERFACE_MAP_ENTRY(nsIFolderListener)
+   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END
 
 
 nsresult
 nsMessengerOSXIntegration::Init()
 {
+  // need to register a named Growl notification
   nsresult rv;
+  nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
+  if (NS_SUCCEEDED(rv))
+    observerService->AddObserver(this, "before-growl-registration", PR_FALSE);
     
   nsCOMPtr <nsIMsgAccountManager> accountManager = 
     do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv,rv);
 
   // because we care if the default server changes
   rv = accountManager->AddRootFolderListener(this);
   NS_ENSURE_SUCCESS(rv,rv);
 
   nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv,rv);
 
   // because we care if the unread total count changes
-  rv = mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
-  NS_ENSURE_SUCCESS(rv,rv);
-
-  return NS_OK;
+  return mailSession->AddFolderListener(this, nsIFolderListener::boolPropertyChanged | nsIFolderListener::intPropertyChanged);
 }
 
 NS_IMETHODIMP
 nsMessengerOSXIntegration::OnItemPropertyChanged(nsIMsgFolder *, nsIAtom *, char const *, char const *)
 {
   return NS_OK;
 }
 
@@ -132,47 +178,246 @@ nsMessengerOSXIntegration::OnItemUnichar
 }
 
 NS_IMETHODIMP
 nsMessengerOSXIntegration::OnItemRemoved(nsIMsgFolder *, nsISupports *)
 {
   return NS_OK;
 }
 
-PRInt32 nsMessengerOSXIntegration::CountNewMessages()
+NS_IMETHODIMP
+nsMessengerOSXIntegration::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData)
+{
+  if (!strcmp(aTopic, "alertfinished"))
+    return OnAlertFinished(nsnull);
+
+  if (!strcmp(aTopic, "alertclickcallback"))
+    return OnAlertClicked();
+
+  // register named Growl notification for new mail alerts.
+  if (!strcmp(aTopic, "before-growl-registration"))
+  {
+      printf("\n\n\nbefore-growl-registration\n\n\n");
+    nsresult rv;
+    nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1", &rv);
+    if (NS_SUCCEEDED(rv))
+      observerService->RemoveObserver(this, "before-growl-registration");
+
+    nsCOMPtr<nsINotificationsList> notifications = do_QueryInterface(aSubject, &rv);
+    if (NS_SUCCEEDED(rv))
+    {
+      printf("...trying to get string bundle...\n");
+      nsCOMPtr<nsIStringBundle> bundle; 
+      GetStringBundle(getter_AddRefs(bundle));
+      if (bundle)
+      { 
+        nsString growlNotification;
+        bundle->GetStringFromName(NS_LITERAL_STRING("growlNotification").get(), getter_Copies(growlNotification));
+        notifications->AddNotification(growlNotification, PR_TRUE);
+      }
+    }
+  }
+  return NS_OK;
+}
+
+PRInt32
+nsMessengerOSXIntegration::CountNewMessages()
 {
   // iterate over all the folders in mFoldersWithNewMail
   nsCOMPtr<nsIMsgFolder> folder;
   nsCOMPtr<nsIWeakReference> weakReference;
   PRInt32 numNewMessages = 0;
   PRInt32 totalNewMessages = 0;
   
   PRUint32 count = 0;
   mFoldersWithNewMail->Count(&count);
 
   for (PRUint32 index = 0; index < count; index++)
   {
     weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
     folder = do_QueryReferent(weakReference);
     if (folder)
     {
-
       numNewMessages = 0;   
       folder->GetNumNewMessages(PR_TRUE, &numNewMessages);
       totalNewMessages += numNewMessages;
     } // if we got a folder
   } // for each folder
 
   return totalNewMessages;
-  
+}
+
+nsresult
+nsMessengerOSXIntegration::GetStringBundle(nsIStringBundle **aBundle)
+{
+  NS_ENSURE_ARG_POINTER(aBundle);
+  nsresult rv;
+  nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+  nsCOMPtr<nsIStringBundle> bundle;
+  if (bundleService && NS_SUCCEEDED(rv))
+    bundleService->CreateBundle("chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle));
+  bundle.swap(*aBundle);
+  return rv;
+}
+
+void
+nsMessengerOSXIntegration::FillToolTipInfo()
+{
+  nsCOMPtr<nsIWeakReference> weakReference = do_QueryElementAt(mFoldersWithNewMail, 0);
+  nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(weakReference);
+  if (folder)
+  {
+    nsString accountName;
+    folder->GetPrettiestName(accountName);
+
+    nsCOMPtr<nsIStringBundle> bundle; 
+    GetStringBundle(getter_AddRefs(bundle));
+    if (bundle)
+    { 
+      PRInt32 numNewMessages = 0;   
+      folder->GetNumNewMessages(PR_TRUE, &numNewMessages);
+      nsAutoString numNewMsgsText;     
+      numNewMsgsText.AppendInt(numNewMessages);
+
+      const PRUnichar *formatStrings[] =
+      {
+        numNewMsgsText.get(),       
+      };
+     
+      nsString finalText; 
+      if (numNewMessages == 1)
+        bundle->FormatStringFromName(NS_LITERAL_STRING("biffNotification_message").get(), formatStrings, 1, getter_Copies(finalText));
+      else
+        bundle->FormatStringFromName(NS_LITERAL_STRING("biffNotification_messages").get(), formatStrings, 1, getter_Copies(finalText));
+
+      ShowAlertMessage(accountName, finalText, EmptyCString());
+    } // if we got a bundle
+  } // if we got a folder
 }
 
-nsresult nsMessengerOSXIntegration::OnAlertFinished(const PRUnichar * aAlertCookie)
+nsresult
+nsMessengerOSXIntegration::ShowAlertMessage(const nsAString& aAlertTitle,
+                                            const nsAString& aAlertText,
+                                            const nsACString& aFolderURI)
+{
+  // if we are alredy in the process of showing an alert, don't try to show another one
+  if (mAlertInProgress)
+    return NS_OK;
+
+  nsresult rv;
+  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  PRBool showAlert = PR_TRUE;
+  prefBranch->GetBoolPref(kBiffShowAlertPref, &showAlert);
+
+  if (showAlert)
+  {
+    nsCOMPtr<nsIAlertsService> alertsService (do_GetService(NS_ALERTSERVICE_CONTRACTID, &rv));
+    if (NS_SUCCEEDED(rv))
+    {
+      nsCOMPtr<nsIStringBundle> bundle; 
+      GetStringBundle(getter_AddRefs(bundle));
+      if (bundle)
+      { 
+        nsString growlNotification;
+        bundle->GetStringFromName(NS_LITERAL_STRING("growlNotification").get(), getter_Copies(growlNotification));
+        rv = alertsService->ShowAlertNotification(NS_LITERAL_STRING(kNewMailAlertIcon), aAlertTitle,
+                                                  aAlertText, PR_TRUE, NS_ConvertASCIItoUTF16(aFolderURI),
+                                                  this, growlNotification);
+        mAlertInProgress = PR_TRUE;
+      }
+    }
+  }
+   
+  if (!showAlert || NS_FAILED(rv))
+    OnAlertFinished(nsnull);
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aFolder,
+                                                    nsIAtom *aProperty,
+                                                    PRInt32 aOldValue,
+                                                    PRInt32 aNewValue)
 {
-  nsresult rv = NS_OK;
+   // if we got new mail show an alert
+  if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
+  {
+    NS_ENSURE_TRUE(aFolder, NS_OK);
+
+    if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail)
+    {
+      // if the icon is not already visible, only show a system tray icon if
+      // we are performing biff (as opposed to the user getting new mail)
+      if (!mBiffIconVisible)
+      {
+        PRBool performingBiff = PR_FALSE;
+        nsCOMPtr<nsIMsgIncomingServer> server;
+        aFolder->GetServer(getter_AddRefs(server));
+        if (server)
+          server->GetPerformingBiff(&performingBiff);
+        if (!performingBiff)
+          return NS_OK; // kick out right now...
+      }
+
+      nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aFolder);
+
+      if (mFoldersWithNewMail->IndexOf(weakFolder) == kNotFound)
+          mFoldersWithNewMail->AppendElement(weakFolder);
+
+      FillToolTipInfo();
+    }
+    else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
+    {
+      // we are always going to remove the icon whenever we get our first no
+      // mail notification.
+      mFoldersWithNewMail->Clear();
+      if (mBiffIconVisible)
+      {
+        RestoreApplicationDockTileImage();
+        mBiffIconVisible = PR_FALSE;
+      }
+    }
+  } // if the biff property changed
+
+  return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::OnAlertClicked()
+{
+#ifdef MOZ_THUNDERBIRD
+  nsresult rv;
+  nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv,rv);
+  
+  nsCOMPtr<nsIMsgWindow> topMostMsgWindow;
+  rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(topMostMsgWindow));
+  if (topMostMsgWindow)
+  {
+    nsCOMPtr<nsIDOMWindowInternal> domWindow;
+    rv = topMostMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    domWindow->Focus();
+  }
+#else
+  nsCString folderURI;
+  GetFirstFolderWithNewMail(folderURI);
+  openMailWindow(folderURI);
+#endif
+  return NS_OK;
+}
+
+nsresult
+nsMessengerOSXIntegration::OnAlertFinished(const PRUnichar * aAlertCookie)
+{
+  nsresult rv;
   nsCOMPtr<nsIPrefBranch> prefBranch (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
   
   PRBool bounceDockIcon = PR_FALSE; 
   prefBranch->GetBoolPref("mail.biff.animate_dock_icon", &bounceDockIcon);
 
   // This will call GetAttention(), which will bounce the dock icon.
   if (!mSuppressBiffIcon)
@@ -195,17 +440,16 @@ nsresult nsMessengerOSXIntegration::OnAl
     
     // use OverlayApplicationDockTileImage
     // -- you'll have to pass it a CGImage, and somehow we have to
     // create the CGImage with the numbers. tricky    
     PRInt32 totalNewMessages = CountNewMessages();
     CGContextRef context = ::BeginCGContextForApplicationDockTile();
     
     // Draw a circle.
-
     ::CGContextBeginPath(context);
     ::CGContextAddArc(context, 95.0, 95.0, 25.0, 0.0, 2 * M_PI, true);
     ::CGContextClosePath(context);
 
     // use #2fc600 for the color.
     ::CGContextSetRGBFillColor(context, 0.184, 0.776, 0.0, 1);
 
     //::CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.7);
@@ -249,62 +493,60 @@ nsresult nsMessengerOSXIntegration::OnAl
     
     ATSUAttributeTag tags[3] = { kATSUFontTag, kATSUSizeTag, kATSUColorTag };
     ByteCount valueSizes[3] = { sizeof(ATSUFontID), sizeof(Fixed),
                                 sizeof(RGBColor) };
     ATSUAttributeValuePtr values[3] = { &fmFont, &size, &white };
 
     err = ::ATSUSetAttributes(style, 3, tags, valueSizes, values);
     if (err != noErr) {
-        NS_WARNING("ATSUSetAttributes failed");
-        ::ATSUDisposeStyle(style);
-        ::EndCGContextForApplicationDockTile(context);
-
-        return NS_ERROR_FAILURE;
-      }
+      NS_WARNING("ATSUSetAttributes failed");
+      ::ATSUDisposeStyle(style);
+      ::EndCGContextForApplicationDockTile(context);
+      return NS_ERROR_FAILURE;
+    }
 
     UniCharCount runLengths = kATSUToTextEnd;
     ATSUTextLayout textLayout;
     err = ::ATSUCreateTextLayoutWithTextPtr(total.get(),
                                             kATSUFromTextBeginning,
                                             kATSUToTextEnd, total.Length(), 1,
                                             &runLengths, &style, &textLayout);
     if (err != noErr) 
 	{
-        NS_WARNING("ATSUCreateTextLayoutWithTextPtr failed");
-        ::ATSUDisposeStyle(style);
-        ::EndCGContextForApplicationDockTile(context);
-
-        return NS_ERROR_FAILURE;
+      NS_WARNING("ATSUCreateTextLayoutWithTextPtr failed");
+      ::ATSUDisposeStyle(style);
+      ::EndCGContextForApplicationDockTile(context);
+      return NS_ERROR_FAILURE;
     }
 
     ATSUAttributeTag layoutTags[1] = { kATSUCGContextTag };
     ByteCount layoutValueSizes[1] = { sizeof(CGContextRef) };
     ATSUAttributeValuePtr layoutValues[1] = { &context };
 
     err = ::ATSUSetLayoutControls(textLayout, 1, layoutTags, layoutValueSizes,
                                   layoutValues);
     if (err != noErr) 
 	{
-        NS_WARNING("ATSUSetLayoutControls failed");
-        ::ATSUDisposeStyle(style);
-        ::EndCGContextForApplicationDockTile(context);
-        return NS_ERROR_FAILURE;
+      NS_WARNING("ATSUSetLayoutControls failed");
+      ::ATSUDisposeStyle(style);
+      ::EndCGContextForApplicationDockTile(context);
+      return NS_ERROR_FAILURE;
     }
 
     Rect boundingBox;
     err = ::ATSUMeasureTextImage(textLayout, kATSUFromTextBeginning,
                                  kATSUToTextEnd, Long2Fix(0), Long2Fix(0),
                                  &boundingBox);
     if (err != noErr) 
 	{
-        NS_WARNING("ATSUMeasureTextImage failed");
-        ::ATSUDisposeStyle(style);
-        ::EndCGContextForApplicationDockTile(context);
-        return NS_ERROR_FAILURE;
+      NS_WARNING("ATSUMeasureTextImage failed");
+      ::ATSUDisposeStyle(style);
+      ::EndCGContextForApplicationDockTile(context);
+      return NS_ERROR_FAILURE;
     }
 
     // Center text inside circle
     err = ::ATSUDrawText(textLayout, kATSUFromTextBeginning, kATSUToTextEnd,
                          Long2Fix(90 - (boundingBox.right - boundingBox.left) / 2),
                          Long2Fix(95 - (boundingBox.bottom - boundingBox.top) / 2));
 
     ::ATSUDisposeStyle(style);
@@ -313,17 +555,17 @@ nsresult nsMessengerOSXIntegration::OnAl
     ::CGContextFlush(context);
 
     ::EndCGContextForApplicationDockTile(context);
     mBiffIconVisible = PR_TRUE;
   }
 
   mSuppressBiffIcon = PR_FALSE;
   mAlertInProgress = PR_FALSE;
-  return NS_OK;
+  return rv; // was NS_OK;
 }
 
 NS_IMETHODIMP
 nsMessengerOSXIntegration::OnItemPropertyFlagChanged(nsIMsgDBHdr *item, nsIAtom *property, PRUint32 oldFlag, PRUint32 newFlag)
 {
   return NS_OK;
 }
 
@@ -343,67 +585,68 @@ nsMessengerOSXIntegration::OnItemBoolPro
 }
 
 NS_IMETHODIMP
 nsMessengerOSXIntegration::OnItemEvent(nsIMsgFolder *, nsIAtom *)
 {
   return NS_OK;
 }
 
-NS_IMETHODIMP
-nsMessengerOSXIntegration::OnItemIntPropertyChanged(nsIMsgFolder *aItem, nsIAtom *aProperty, PRInt32 aOldValue, PRInt32 aNewValue)
+// get the first top level folder which we know has new mail, then enumerate over all the subfolders
+// looking for the first real folder with new mail. Return the folderURI for that folder.
+nsresult
+nsMessengerOSXIntegration::GetFirstFolderWithNewMail(nsACString& aFolderURI)
 {
-  // if we got new mail bounce the Dock icon and/or apply badge to Dock icon
-  if (mBiffStateAtom == aProperty && mFoldersWithNewMail)
-  {
-    if (aNewValue == nsIMsgFolder::nsMsgBiffState_NewMail) 
-    {
-      // if the icon is not already visible, only show a system tray icon iff 
-      // we are performing biff (as opposed to the user getting new mail)
-      if (!mBiffIconVisible)
-      {
-        PRBool performingBiff = PR_FALSE;
-        nsCOMPtr<nsIMsgIncomingServer> server;
-        aItem->GetServer(getter_AddRefs(server));
-        if (server)
-          server->GetPerformingBiff(&performingBiff);
-        if (!performingBiff) 
-          return NS_OK; // kick out right now...
-      }
+  nsresult rv;
+  NS_ENSURE_TRUE(mFoldersWithNewMail, NS_ERROR_FAILURE);
+
+  nsCOMPtr<nsIMsgFolder> folder;
+  nsCOMPtr<nsIWeakReference> weakReference;
+  PRInt32 numNewMessages = 0;
+
+  PRUint32 count = 0;
+  mFoldersWithNewMail->Count(&count);
+
+  if (!count)  // kick out if we don't have any folders with new mail
+    return NS_OK;
+
+  weakReference = do_QueryElementAt(mFoldersWithNewMail, 0);
+  folder = do_QueryReferent(weakReference);
 
-      nsCOMPtr<nsIWeakReference> weakFolder = do_GetWeakReference(aItem); 
-      // remove the element if it is already in the array....
-      PRUint32 count = 0;
-      PRUint32 index = 0; 
-      mFoldersWithNewMail->Count(&count);
-      nsCOMPtr<nsIMsgFolder> oldFolder;
-      nsCOMPtr<nsIWeakReference> weakReference;
-      for (index = 0; index < count; index++)
-      {
-        weakReference = do_QueryElementAt(mFoldersWithNewMail, index);
-        oldFolder = do_QueryReferent(weakReference);
-        if (oldFolder == aItem) // if they point to the same folder
-          break;
-        oldFolder = nsnull;
-      }
+  if (folder)
+  {
+    nsCOMPtr<nsIMsgFolder> msgFolder;
+    // enumerate over the folders under this root folder till we find one with new mail....
+    nsCOMPtr<nsISupportsArray> allFolders;
+    NS_NewISupportsArray(getter_AddRefs(allFolders));
+    rv = folder->ListDescendents(allFolders);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-	 if (oldFolder)
-       mFoldersWithNewMail->ReplaceElementAt(weakFolder, index);
-	 else
-       mFoldersWithNewMail->AppendElement(weakFolder);
-      // now regenerate the tooltip
-      OnAlertFinished(nsnull);  
-    }
-    else if (aNewValue == nsIMsgFolder::nsMsgBiffState_NoMail)
-	{
-      // we are always going to remove the icon whenever we get our first no mail
-      // notification. 
-      mFoldersWithNewMail->Clear(); 
-      if (mBiffIconVisible) 
+    nsCOMPtr<nsIEnumerator> enumerator;
+    allFolders->Enumerate(getter_AddRefs(enumerator));
+    if (enumerator)
+    {
+      nsCOMPtr<nsISupports> supports;
+      nsresult more = enumerator->First();
+      while (NS_SUCCEEDED(more))
       {
-        RestoreApplicationDockTileImage();
-        mBiffIconVisible = PR_FALSE;
-      }
-    }
-  } // if the biff property changed
-  
+        rv = enumerator->CurrentItem(getter_AddRefs(supports));
+        if (supports)
+        {
+          msgFolder = do_QueryInterface(supports, &rv);
+          if (msgFolder)
+          {
+            numNewMessages = 0;
+            msgFolder->GetNumNewMessages(PR_FALSE, &numNewMessages);
+            if (numNewMessages)
+              break; // kick out of the while loop
+            more = enumerator->Next();
+          }
+        } // if we have a folder
+      }  // if we have more potential folders to enumerate
+    }  // if enumerator
+
+    if (msgFolder)
+      msgFolder->GetURI(aFolderURI);
+  }
+
   return NS_OK;
 }
--- a/mailnews/base/src/nsMessengerOSXIntegration.h
+++ b/mailnews/base/src/nsMessengerOSXIntegration.h
@@ -43,37 +43,48 @@
 #include "nsIMessengerOSIntegration.h"
 #include "nsIFolderListener.h"
 #include "nsIAtom.h"
 #include "nsITimer.h"
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsInt64.h"
 #include "nsISupportsArray.h"
+#include "nsIObserver.h"
+#include "nsIAlertsService.h"
 
 #define NS_MESSENGEROSXINTEGRATION_CID \
   {0xaa83266, 0x4225, 0x4c4b, \
   {0x93, 0xf8, 0x94, 0xb1, 0x82, 0x58, 0x6f, 0x93}}
 
+class nsIStringBundle;
+
 class nsMessengerOSXIntegration : public nsIMessengerOSIntegration,
-                                  public nsIFolderListener
+                                  public nsIFolderListener,
+                                  public nsIObserver
 {
 public:
   nsMessengerOSXIntegration();
   virtual ~nsMessengerOSXIntegration();
   virtual nsresult Init();
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMESSENGEROSINTEGRATION
   NS_DECL_NSIFOLDERLISTENER
+  NS_DECL_NSIOBSERVER
 
 private:
   nsCOMPtr<nsISupportsArray> mFoldersWithNewMail;  // keep track of all the root folders with pending new mail
   nsCOMPtr<nsIAtom> mBiffStateAtom;
   PRInt32 CountNewMessages();
+  nsresult ShowAlertMessage(const nsAString& aAlertTitle, const nsAString& aAlertText, const nsACString& aFolderURI);
   nsresult OnAlertFinished(const PRUnichar * aAlertCookie);
+  nsresult OnAlertClicked();
+  nsresult GetStringBundle(nsIStringBundle **aBundle);
+  void FillToolTipInfo();
+  nsresult GetFirstFolderWithNewMail(nsACString& aFolderURI);
 
   PRPackedBool mBiffIconVisible;
   PRPackedBool mSuppressBiffIcon;
   PRPackedBool mAlertInProgress;
 };
 
 #endif // __nsMessengerOSXIntegration_h
--- a/mailnews/build/Makefile.in
+++ b/mailnews/build/Makefile.in
@@ -91,16 +91,20 @@ REQUIRES	= xpcom \
 		  string \
 		  txmgr \
 		  widget \
 		  webbrwsr \
 		  uconv \
 		  uriloader \
 		  $(NULL)
 
+ifeq ($(OS_TARGET),Darwin)
+REQUIRES += alerts
+endif
+
 ifdef MOZ_LDAP_XPCOM
 REQUIRES	+= mozldap \
                $(NULL)
 
 DEFINES		+= -DMOZ_LDAP_XPCOM
 endif
 
 CPPSRCS		= nsMailModule.cpp
--- a/suite/locales/en-US/chrome/mailnews/messenger.properties
+++ b/suite/locales/en-US/chrome/mailnews/messenger.properties
@@ -399,16 +399,19 @@ errorOpenMessageForMessageIdMessage=Mess
 confirmPhishingTitle=Email Scam Alert
 #LOCALIZATION NOTE %1$S is the brand name, %2$S is the host name of the url being visited
 confirmPhishingUrl1=%1$S thinks this site is suspicious! It may be trying to impersonate the web page you want to visit. Most legitimate sites use names instead of numbers. Are you sure you want to visit %2$S?
 confirmPhishingUrl2=%1$S thinks this site is suspicious! It may be trying to impersonate the web page you want to visit. Are you sure you want to visit %2$S?
 
 #LOCALIZATION NOTE %1$S is the e-mail address of the person we will allow remote content for
 alwaysLoadRemoteContentForSender=Click here to always load remote content from %1$S.
 
+# Strings for growl notifications on Mac OS X
+growlNotification=New Mail
+
 # mailCommands.js
 emptyJunkTitle=Confirm
 emptyJunkMessage=Are you sure you want to permanently delete all messages and subfolders in the Junk folder?
 emptyJunkDontAsk=Don't ask me again.
 emptyTrashTitle=Confirm
 emptyTrashMessage=Are you sure you want to permanently delete all messages and subfolders in the Trash folder?
 emptyTrashDontAsk=Don't ask me again.