Bug 711173 - Fix for UI freeze on feed biff/get new messages; renovate newsblog.js for fun. r=bienvenu
authoralta88@gmail.com
Mon, 13 Feb 2012 20:45:12 +0000
changeset 10821 42b7c8b861b47e8cde7f165b94f5c84ddcd3dac3
parent 10820 a489b0bb0a7f639408b7e00f76a5150acdf00c47
child 10822 1717874b0963808d65d3bffa1a750046c11f8068
push id463
push userbugzilla@standard8.plus.com
push dateTue, 24 Apr 2012 17:34:51 +0000
treeherdercomm-beta@e53588e8f7b0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbienvenu
bugs711173
Bug 711173 - Fix for UI freeze on feed biff/get new messages; renovate newsblog.js for fun. r=bienvenu
mailnews/extensions/newsblog/content/utils.js
mailnews/extensions/newsblog/js/newsblog.js
mailnews/local/src/nsRssIncomingServer.cpp
--- a/mailnews/extensions/newsblog/content/utils.js
+++ b/mailnews/extensions/newsblog/content/utils.js
@@ -33,16 +33,190 @@
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK ******
 
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource:///modules/mailServices.js");
+Cu.import("resource:///modules/gloda/log4moz.js");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var FeedUtils = {
+  kBiffMinutesDefault: 100,
+  kNewsBlogSuccess: 0,
+  // Usually means there was an error trying to parse the feed.
+  kNewsBlogInvalidFeed: 1,
+  // Generic networking failure when trying to download the feed.
+  kNewsBlogRequestFailure: 2,
+  kNewsBlogFeedIsBusy: 3,
+  // There are no new articles for this feed
+  kNewsBlogNoNewItems: 4,
+
+  // Progress glue code.  Acts as a go between the RSS back end and the mail
+  // window front end determined by the aMsgWindow parameter passed into
+  // nsINewsBlogFeedDownloader.
+  progressNotifier: {
+    mSubscribeMode: false,
+    mMsgWindow: null,
+    mStatusFeedback: null,
+    mFeeds: {},
+    // Keeps track of the total number of feeds we have been asked to download.
+    // This number may not reflect the # of entries in our mFeeds array because
+    // not all feeds may have reported in for the first time.
+    mNumPendingFeedDownloads: 0,
+
+    init: function(aMsgWindow, aSubscribeMode)
+    {
+      if (!this.mNumPendingFeedDownloads)
+      {
+        // If we aren't already in the middle of downloading feed items.
+        this.mStatusFeedback = aMsgWindow ? aMsgWindow.statusFeedback : null;
+        this.mSubscribeMode = aSubscribeMode;
+        this.mMsgWindow = aMsgWindow;
+
+        if (this.mStatusFeedback)
+        {
+          this.mStatusFeedback.startMeteors();
+          this.mStatusFeedback.showStatusString(
+            FeedUtils.strings.GetStringFromName(
+              aSubscribeMode ? "subscribe-validating-feed" :
+                               "newsblog-getNewMsgsCheck"));
+        }
+      }
+    },
+
+    downloaded: function(feed, aErrorCode)
+    {
+      FeedUtils.log.debug("downloaded: feed:errorCode - " +
+                          feed.name+" : "+aErrorCode);
+      if (this.mSubscribeMode && aErrorCode == FeedUtils.kNewsBlogSuccess)
+      {
+        // If we get here we should always have a folder by now, either in
+        // feed.folder or FeedItems created the folder for us.
+        updateFolderFeedUrl(feed.folder, feed.url, false);
+
+        // Add feed just adds the feed to the subscription UI and flushes the
+        // datasource.
+        addFeed(feed.url, feed.name, feed.folder);
+
+        // Nice touch: select the folder that now contains the newly subscribed
+        // feed.  This is particularly nice if we just finished subscribing
+        // to a feed URL that the operating system gave us.
+        this.mMsgWindow.windowCommands.selectFolder(feed.folder.URI);
+      }
+      else if (feed.folder && aErrorCode != FeedUtils.kNewsBlogFeedIsBusy)
+        // Free msgDatabase after new mail biff is set; if busy let the next
+        // result do the freeing.  Otherwise new messages won't be indicated.
+        feed.folder.msgDatabase = null;
+
+      if (this.mStatusFeedback)
+      {
+        if (aErrorCode == FeedUtils.kNewsBlogNoNewItems)
+          this.mStatusFeedback.showStatusString(
+            FeedUtils.strings.GetStringFromName("newsblog-noNewArticlesForFeed"));
+        else if (aErrorCode == FeedUtils.kNewsBlogInvalidFeed)
+          this.mStatusFeedback.showStatusString(
+            FeedUtils.strings.formatStringFromName("newsblog-feedNotValid",
+                                                   [feed.url], 1));
+        else if (aErrorCode == FeedUtils.kNewsBlogRequestFailure)
+          this.mStatusFeedback.showStatusString(
+            FeedUtils.strings.formatStringFromName("newsblog-networkError",
+                                                   [feed.url], 1));
+        this.mStatusFeedback.stopMeteors();
+      }
+
+      if (!--this.mNumPendingFeedDownloads)
+      {
+        this.mFeeds = {};
+        this.mSubscribeMode = false;
+
+        // Should we do this on a timer so the text sticks around for a little
+        // while?  It doesnt look like we do it on a timer for newsgroups so
+        // we'll follow that model.  Don't clear the status text if we just
+        // dumped an error to the status bar!
+        if (aErrorCode == FeedUtils.kNewsBlogSuccess && this.mStatusFeedback)
+          this.mStatusFeedback.showStatusString("");
+      }
+    },
+
+    // This gets called after the RSS parser finishes storing a feed item to
+    // disk. aCurrentFeedItems is an integer corresponding to how many feed
+    // items have been downloaded so far.  aMaxFeedItems is an integer
+    // corresponding to the total number of feed items to download
+    onFeedItemStored: function (feed, aCurrentFeedItems, aMaxFeedItems)
+    {
+      // We currently don't do anything here.  Eventually we may add status
+      // text about the number of new feed articles received.
+
+      if (this.mSubscribeMode && this.mStatusFeedback)
+      {
+        // If we are subscribing to a feed, show feed download progress.
+        this.mStatusFeedback.showStatusString(
+          FeedUtils.strings.formatStringFromName("subscribe-gettingFeedItems",
+                                                 [aCurrentFeedItems, aMaxFeedItems], 2));
+        this.onProgress(feed, aCurrentFeedItems, aMaxFeedItems);
+      }
+    },
+
+    onProgress: function(feed, aProgress, aProgressMax)
+    {
+      if (feed.url in this.mFeeds)
+        // Have we already seen this feed?
+        this.mFeeds[feed.url].currentProgress = aProgress;
+      else
+        this.mFeeds[feed.url] = {currentProgress: aProgress,
+                                 maxProgress: aProgressMax};
+
+      this.updateProgressBar();
+    },
+
+    updateProgressBar: function()
+    {
+      var currentProgress = 0;
+      var maxProgress = 0;
+      for (let index in this.mFeeds)
+      {
+        currentProgress += this.mFeeds[index].currentProgress;
+        maxProgress += this.mFeeds[index].maxProgress;
+      }
+
+      // If we start seeing weird "jumping" behavior where the progress bar
+      // goes below a threshold then above it again, then we can factor a
+      // fudge factor here based on the number of feeds that have not reported
+      // yet and the avg progress we've already received for existing feeds.
+      // Fortunately the progressmeter is on a timer and only updates every so
+      // often.  For the most part all of our request have initial progress
+      // before the UI actually picks up a progress value. 
+      if (this.mStatusFeedback)
+      {
+        let progress = (currentProgress * 100) / maxProgress;
+        this.mStatusFeedback.showProgress(progress);
+      }
+    }
+  }
+};
+
+XPCOMUtils.defineLazyGetter(FeedUtils, "log", function() {
+  return Log4Moz.getConfiguredLogger("Feeds");
+});
+
+XPCOMUtils.defineLazyGetter(FeedUtils, "strings", function() {
+  return Services.strings.createBundle(
+    "chrome://messenger-newsblog/locale/newsblog.properties");
+});
+
 // Whether or not to dump debugging messages to the console.
 const DEBUG = false;
 var debug;
 if (DEBUG)
   debug = function(msg) { dump(' -- FZ -- : ' + msg + '\n'); }
 else
   debug = function() {}
 
@@ -197,34 +371,45 @@ function getFeedUrlsInFolder(aFolder)
     return feedurls.split(kFeedUrlDelimiter);
 
   // Go to msgDatabase for the property, make sure to handle errors.
   let msgDb;
   try {
     msgDb = aFolder.msgDatabase;
   }
   catch (ex) {}
-  if (!msgDb || !msgDb.dBFolderInfo) {
-    try {
-      msgDb = aFolder.QueryInterface(Components.interfaces.nsIMsgLocalMailFolder)
-                     .getDatabaseWOReparse();
-    }
-    catch (ex) {}
-  }
   if (msgDb && msgDb.dBFolderInfo) {
     feedurls = msgDb.dBFolderInfo.getCharProperty("feedUrl");
     // Clean up the feedUrl string.
     feedurls.split(kFeedUrlDelimiter).forEach(
       function(url) {
         if (url && feedUrlArray.indexOf(url) == -1)
           feedUrlArray.push(url);
       });
 
     feedurls = feedUrlArray.join(kFeedUrlDelimiter);
     if (feedurls) {
+      // Do a onetime per folder re-sync of the feeds db here based on the
+      // urls in the feedUrl property.
+      let ds = getSubscriptionsDS(aFolder.server);
+      let resource = rdf.GetResource(aFolder.URI);
+      feedUrlArray.forEach(
+        function(url) {
+          let id = rdf.GetResource(url);
+          // Get the node for the current folder URI.
+          let node = ds.GetTarget(id, FZ_DESTFOLDER, true);
+          if (node)
+            ds.Change(id, FZ_DESTFOLDER, node, resource);
+          else
+            addFeed(url, resource.name, resource);
+          FeedUtils.log.debug("getFeedUrlsInFolder: sync folder:url - " +
+                              aFolder.name+" : "+url);
+      });
+      ds.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
+
       // Set property on folder so we don't come here ever again.
       aFolder.setStringProperty("feedUrl", feedurls);
       aFolder.msgDatabase = null;
 
       return feedUrlArray.length ? feedUrlArray : null;
     }
   }
   else {
--- a/mailnews/extensions/newsblog/js/newsblog.js
+++ b/mailnews/extensions/newsblog/js/newsblog.js
@@ -39,270 +39,304 @@
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var gExternalScriptsLoaded = false;
 
 var nsNewsBlogFeedDownloader =
 {
-  downloadFeed: function(aUrl, aFolder, aQuickMode, aTitle, aUrlListener, aMsgWindow)
+  downloadFeed: function(aA, aFolder, aB, aC, aUrlListener, aMsgWindow)
   {
-    const Ci = Components.interfaces;
-
     if (!gExternalScriptsLoaded)
       loadScripts();
 
     // We don't yet support the ability to check for new articles while we are
-    // in the middle of  subscribing to a feed. For now, abort the check for
+    // in the middle of subscribing to a feed. For now, abort the check for
     // new feeds.
-    if (progressNotifier.mSubscribeMode)
+    if (FeedUtils.progressNotifier.mSubscribeMode)
     {
-      debug('Aborting RSS New Mail Check. Feed subscription in progress\n');
+      FeedUtils.log.warn("downloadFeed: Aborting RSS New Mail Check. " +
+                         "Feed subscription in progress\n");
       return;
     }
 
     let feedUrlArray = getFeedUrlsInFolder(aFolder);
 
-    // Return if there are no feedUrls for the folder in the feeds database or
-    // the folder is in Trash.
-    if (!feedUrlArray ||
+    // Return if there are no feedUrls for the base folder in the feeds
+    // database, the base folder has no subfolders, or the folder is in Trash.
+    if ((!feedUrlArray && !aFolder.hasSubFolders) ||
         aFolder.isSpecialFolder(Ci.nsMsgFolderFlags.Trash, true))
       return;
 
-    // Ensure msgDatabase open for new message processing.
-    let msgDb;
-    try {
-      msgDb = aFolder.QueryInterface(Ci.nsIMsgLocalMailFolder)
-                     .getDatabaseWOReparse();
-    }
-    catch (ex) {}
-    if (!msgDb) {
-      // Forcing a reparse with listener here is the last resort.  This is
-      // not implemented; it is currently not done and may be unnecessary given
-      // other msgDatabse sync fixes.
+    let allFolders = Cc["@mozilla.org/supports-array;1"].
+                     createInstance(Ci.nsISupportsArray);
+    // Add the base folder; it does not get added by ListDescendents.
+    allFolders.AppendElement(aFolder);
+    aFolder.ListDescendents(allFolders);
+    let numFolders = allFolders.Count();
+    let trashFolder =
+        aFolder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+
+    function feeder() {
+      for (let i = 0; i < numFolders; i++) {
+        let folder = allFolders.GetElementAt(i).QueryInterface(Ci.nsIMsgFolder);
+        FeedUtils.log.debug("downloadFeed: START x/# foldername:uri - "+
+                            (i+1)+"/"+ numFolders+" "+
+                            folder.name+":"+folder.URI);
+        let feedUrlArray = getFeedUrlsInFolder(folder);
+        // Continue if there are no feedUrls for the folder in the feeds
+        // database.  All folders in Trash are now unsubscribed, so perhaps
+        // we may not want to check that here each biff each folder.
+        if (!feedUrlArray ||
+            (aFolder.isServer && trashFolder && trashFolder.isAncestorOf(folder)))
+          continue;
+
+        FeedUtils.log.debug("downloadFeed: CONTINUE foldername:urlArray - "+
+                            folder.name+":"+feedUrlArray);
+
+        FeedUtils.progressNotifier.init(aMsgWindow, false);
+
+        // Ensure msgDatabase for the folder is open for new message processing.
+        let msgDb;
+        try {
+          msgDb = aFolder.msgDatabase;
+        }
+        catch (ex) {}
+        if (!msgDb) {
+          // Forcing a reparse with listener here is the last resort.  This is
+          // not implemented; it is currently not done and may be unnecessary
+          // given other msgDatabase sync fixes.
+        }
+
+        // We need to kick off a download for each feed.
+        let id, feed;
+        for (let url in feedUrlArray)
+        {
+          if (feedUrlArray[url])
+          {
+            id = rdf.GetResource(feedUrlArray[url]);
+            feed = new Feed(id, folder.server);
+            feed.folder = folder;
+            // Bump our pending feed download count.
+            FeedUtils.progressNotifier.mNumPendingFeedDownloads++;
+            feed.download(true, FeedUtils.progressNotifier);
+            FeedUtils.log.debug("downloadFeed: DOWNLOAD feed url - "+
+                                feedUrlArray[url]);
+          }
+
+          Services.tm.mainThread.dispatch(function() {
+            try {
+              getFeed.next();
+            }
+            catch (ex if ex instanceof StopIteration) {
+              // Finished with all feeds in base folder and its subfolders.
+              FeedUtils.log.debug("downloadFeed: StopIteration");
+            }
+          }, Ci.nsIThread.DISPATCH_NORMAL);
+
+          yield;
+        }
+      }
     }
 
-    // Maybe just pull all these args out of the aFolder DB,
-    // instead of passing them in...
-    let rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
-                        .getService(Ci.nsIRDFService);
-    progressNotifier.init(aMsgWindow, false);
-
-    // We need to kick off a download for each feed.
-    let id, feed;
-    for (let url in feedUrlArray)
-    {
-      if (feedUrlArray[url])
-      {
-        id = rdf.GetResource(feedUrlArray[url]);
-        feed = new Feed(id, aFolder.server);
-        feed.folder = aFolder;
-        gNumPendingFeedDownloads++; // bump our pending feed download count
-        feed.download(true, progressNotifier);
-      }
+    let getFeed = feeder();
+    try {
+      getFeed.next();
+    } 
+    catch (ex if ex instanceof StopIteration) {
+      // Nothing to do.
+      FeedUtils.log.debug("downloadFeed: Nothing to do");
     }
   },
 
   subscribeToFeed: function(aUrl, aFolder, aMsgWindow)
   {
     if (!gExternalScriptsLoaded)
       loadScripts();
 
-    // we don't support the ability to subscribe to several feeds at once yet...
-    // for now, abort the subscription if we are already in the middle of subscribing to a feed
-    // via drag and drop.
-    if (gNumPendingFeedDownloads)
+    // We don't support the ability to subscribe to several feeds at once yet.
+    // For now, abort the subscription if we are already in the middle of
+    // subscribing to a feed via drag and drop.
+    if (FeedUtils.progressNotifier.mNumPendingFeedDownloads)
     {
-      debug('Aborting RSS subscription. Feed downloads already in progress\n');
+      FeedUtils.log.warn("subscribeToFeed: Aborting RSS subscription. " +
+                         "Feed downloads already in progress\n");
       return;
     }
 
-    // if aFolder is null, then use the root folder for the first RSS account
+    // If aFolder is null, then use the root folder for the first RSS account.
     if (!aFolder)
     {
-      var accountManager =
-        Components.classes["@mozilla.org/messenger/account-manager;1"]
-                  .getService(Components.interfaces.nsIMsgAccountManager);
-      var allServers = accountManager.allServers;
-      for (var i = 0; i < allServers.Count() && !aFolder; i++)
+      let allServers = MailServices.accounts.allServers;
+      for (let i = 0; i < allServers.Count() && !aFolder; i++)
       {
-        var currentServer = allServers.QueryElementAt(i, Components.interfaces.nsIMsgIncomingServer);
+        let currentServer = allServers.QueryElementAt(i, Ci.nsIMsgIncomingServer);
         if (currentServer && currentServer.type == 'rss')
-          aFolder = currentServer.rootFolder;      
+          aFolder = currentServer.rootFolder;
       }
     }
 
     // If the user has no RSS account yet, create one; also check then if
-    // the "Local Folders" exist yet and create if necessary
+    // the "Local Folders" exist yet and create if necessary.
     if (!aFolder)
     {
-      var acctMgr = Components.classes["@mozilla.org/messenger/account-manager;1"]
-                              .getService(Components.interfaces.nsIMsgAccountManager);
-
-      var server = acctMgr.createIncomingServer("nobody", "Feeds", "rss");
-
-      server.biffMinutes = 100;
-      server.prettyName = GetNewsBlogStringBundle().GetStringFromName("feeds-accountname");
+      let server = MailServices.accounts.createIncomingServer("nobody",
+                                                              "Feeds",
+                                                              "rss");
+      server.biffMinutes = FeedUtils.kBiffMinutesDefault;
+      server.prettyName = FeedUtils.strings.GetStringFromName("feeds-accountname");
       server.valid = true;
-      var account = acctMgr.createAccount();
+      let account = MailServices.accounts.createAccount();
       account.incomingServer = server;
 
       aFolder = account.incomingServer.rootFolder;
 
-      // Create "Local Folders" if none exist yet as it's guaranteed that those
-      // exist when any account exists
-      var localFolders = null;
-      try  {
-        localFolders = acctMgr.localFoldersServer;
-      } catch (ex) {
+      // Create "Local Folders" if none exist yet as it's guaranteed that
+      // those exist when any account exists.
+      let localFolders = null;
+      try {
+        localFolders = MailServices.accounts.localFoldersServer;
       }
+      catch (ex) {}
 
       if (!localFolders)
-        acctMgr.createLocalMailAccount();
+        MailServices.accounts.createLocalMailAccount();
 
-      // Save new accounts in case of a crash
+      // Save new accounts in case of a crash.
       try {
-        acctMgr.saveAccountInfo();
-      } catch (ex) {
-      }
+        MailServices.accounts.saveAccountInfo();
+      } catch (ex) {}
     }
 
     if (!aMsgWindow)
     {
-      var wmed = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                           .getService(Components.interfaces.nsIWindowMediator);
-
-      var wlist = wmed.getEnumerator("mail:3pane");
+      let wlist = Services.wm.getEnumerator("mail:3pane");
       if (wlist.hasMoreElements())
       {
-        var win = wlist.getNext()
-                       .QueryInterface(Components.interfaces.nsIDOMWindow);
+        let win = wlist.getNext().QueryInterface(Ci.nsIDOMWindow);
         win.focus();
         aMsgWindow = win.msgWindow;
       }
       else
       {
         // If there are no open windows, open one, pass it the URL, and
         // during opening it will subscribe to the feed.
-        var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
-                           .getService(Components.interfaces.nsIWindowWatcher);
-        var arg = Components.classes["@mozilla.org/supports-string;1"]
-                            .createInstance(Components.interfaces.nsISupportsString);
+        let arg = Cc["@mozilla.org/supports-string;1"].
+                  createInstance(Ci.nsISupportsString);
         arg.data = aUrl;
-        ww.openWindow(null, "chrome://messenger/content/", "_blank",
-                      "chrome,dialog=no,all", arg);
+        Services.ww.openWindow(null, "chrome://messenger/content/",
+                               "_blank", "chrome,dialog=no,all", arg);
         return;
       }
     }
 
     // If aUrl is a feed url, then it is either of the form
     // feed://example.org/feed.xml or feed:https://example.org/feed.xml.
     // Replace feed:// with http:// per the spec, then strip off feed:
     // for the second case.
     aUrl = aUrl.replace(/^feed:\x2f\x2f/i, "http://");
     aUrl = aUrl.replace(/^feed:/i, "");
 
-    // make sure we aren't already subscribed to this feed before we attempt to subscribe to it.
+    // Make sure we aren't already subscribed to this feed before we attempt
+    // to subscribe to it.
     if (feedAlreadyExists(aUrl, aFolder.server))
     {
-      aMsgWindow.statusFeedback.showStatusString(GetNewsBlogStringBundle().GetStringFromName('subscribe-feedAlreadySubscribed'));     
+      aMsgWindow.statusFeedback.showStatusString(
+        FeedUtils.strings.GetStringFromName('subscribe-feedAlreadySubscribed'));
       return;
     }
 
-    var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
-              .getService(Components.interfaces.nsIRDFService);
-    
-    var itemResource = rdf.GetResource(aUrl);
-    var feed = new Feed(itemResource, aFolder.server);
+    let itemResource = rdf.GetResource(aUrl);
+    let feed = new Feed(itemResource, aFolder.server);
     feed.quickMode = feed.server.getBoolValue('quickMode');
 
-    if (!aFolder.isServer) // if the root server, create a new folder for the feed
-      feed.folder = aFolder; // user must want us to add this subscription url to an existing RSS folder.
+    // If the root server, create a new folder for the feed.  The user must
+    // want us to add this subscription url to an existing RSS folder.
+    if (!aFolder.isServer)
+      feed.folder = aFolder;
 
-    progressNotifier.init(aMsgWindow, true);
-    gNumPendingFeedDownloads++;
-    feed.download(true, progressNotifier);
+    FeedUtils.progressNotifier.init(aMsgWindow, true);
+    FeedUtils.progressNotifier.mNumPendingFeedDownloads++;
+    feed.download(true, FeedUtils.progressNotifier);
   },
 
   updateSubscriptionsDS: function(aFolder, aUnsubscribe)
   {
     if (!gExternalScriptsLoaded)
       loadScripts();
 
     // An rss folder was just changed, get the folder's feedUrls and update
     // our feed data source.
-    var feedUrlArray = getFeedUrlsInFolder(aFolder);
+    let feedUrlArray = getFeedUrlsInFolder(aFolder);
     if (!feedUrlArray)
       // No feedUrls in this folder.
       return;
 
-    var rdf = Components.classes["@mozilla.org/rdf/rdf-service;1"]
-                        .getService(Components.interfaces.nsIRDFService);
-    var ds = getSubscriptionsDS(aFolder.server);
-
-    for (var url in feedUrlArray)
+    let newFeedUrl, id, resource, node;
+    let ds = getSubscriptionsDS(aFolder.server);
+    let trashFolder =
+        aFolder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+    for (let url in feedUrlArray)
     {
-      var newFeedUrl = feedUrlArray[url];
+      newFeedUrl = feedUrlArray[url];
       if (newFeedUrl)
       {
-        var id = rdf.GetResource(newFeedUrl);
-        // We need to check and see if the folder is a child of the trash...
-        // if it is, then we can treat this as an unsubscribe action.
-        if (aUnsubscribe)
+        id = rdf.GetResource(newFeedUrl);
+        // If explicit delete or move to trash, unsubscribe.
+        if (aUnsubscribe ||
+            (trashFolder && trashFolder.isAncestorOf(aFolder)))
         {
           deleteFeed(id, aFolder.server, aFolder);
         }
         else
         {
-          var resource = rdf.GetResource(aFolder.URI);
-          // get the node for the current folder URI
-          var node = ds.GetTarget(id, FZ_DESTFOLDER, true);
+          resource = rdf.GetResource(aFolder.URI);
+          // Get the node for the current folder URI.
+          node = ds.GetTarget(id, FZ_DESTFOLDER, true);
           if (node)
             ds.Change(id, FZ_DESTFOLDER, node, resource);
           else
             addFeed(newFeedUrl, resource.name, resource);
         }
       }
     } // for each feed url in the folder property
 
-    ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush();
+    ds.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
   },
 
   QueryInterface: function(aIID)
   {
     if (aIID.equals(Components.interfaces.nsINewsBlogFeedDownloader) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 }
 
-var nsNewsBlogAcctMgrExtension = 
-{ 
+var nsNewsBlogAcctMgrExtension =
+{
   name: "newsblog",
   chromePackageName: "messenger-newsblog",
   showPanel: function (server)
   {
     return false;
   },
   QueryInterface: function(aIID)
   {
     if (aIID.equals(Components.interfaces.nsIMsgAccountManagerExtension) ||
         aIID.equals(Components.interfaces.nsISupports))
       return this;
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
-  }  
+  }
 }
 
-function FeedDownloader()
-{
-}
+function FeedDownloader() {}
 
 FeedDownloader.prototype =
 {
   classID: Components.ID("{5c124537-adca-4456-b2b5-641ab687d1f6}"),
   _xpcom_factory:
   {
     createInstance: function (aOuter, aIID)
     {
@@ -313,19 +347,17 @@ FeedDownloader.prototype =
         throw Components.results.NS_ERROR_INVALID_ARG;
 
       // return the singleton
       return nsNewsBlogFeedDownloader.QueryInterface(aIID);
     }
   } // factory
 }; // feed downloader
 
-function AcctMgrExtension()
-{
-}
+function AcctMgrExtension() {}
 
 AcctMgrExtension.prototype =
 {
   classID: Components.ID("{E109C05F-D304-4ca5-8C44-6DE1BFAF1F74}"),
   _xpcom_factory:
   {
     createInstance: function (aOuter, aIID)
     {
@@ -341,154 +373,21 @@ AcctMgrExtension.prototype =
   } // factory
 }; // account manager extension
 
 var components = [FeedDownloader, AcctMgrExtension];
 var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
 
 function loadScripts()
 {
-  var scriptLoader =  Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
+  var scriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                      .getService(Components.interfaces.mozIJSSubScriptLoader);
   if (scriptLoader)
-  { 
+  {
     scriptLoader.loadSubScript("chrome://messenger-newsblog/content/Feed.js");
     scriptLoader.loadSubScript("chrome://messenger-newsblog/content/FeedItem.js");
     scriptLoader.loadSubScript("chrome://messenger-newsblog/content/feed-parser.js");
     scriptLoader.loadSubScript("chrome://messenger-newsblog/content/file-utils.js");
     scriptLoader.loadSubScript("chrome://messenger-newsblog/content/utils.js");
   }
 
   gExternalScriptsLoaded = true;
 }
-
-// Progress glue code. Acts as a go between the RSS back end and the mail window front end
-// determined by the aMsgWindow parameter passed into nsINewsBlogFeedDownloader.
-// gNumPendingFeedDownloads: keeps track of the total number of feeds we have been asked to download
-//                           this number may not reflect the # of entries in our mFeeds array because not all
-//                           feeds may have reported in for the first time...
-var gNumPendingFeedDownloads = 0;
-
-var progressNotifier = {
-  mSubscribeMode: false,
-  mMsgWindow: null, 
-  mStatusFeedback: null,
-  mFeeds: {},
-
-  init: function(aMsgWindow, aSubscribeMode)
-  {
-    if (!gNumPendingFeedDownloads) // if we aren't already in the middle of downloading feed items...
-    {
-      this.mStatusFeedback = aMsgWindow ? aMsgWindow.statusFeedback : null;
-      this.mSubscribeMode = aSubscribeMode;
-      this.mMsgWindow = aMsgWindow;
-
-      if (this.mStatusFeedback)
-      {
-        this.mStatusFeedback.startMeteors();
-        this.mStatusFeedback.showStatusString(aSubscribeMode ?
-          GetNewsBlogStringBundle().GetStringFromName('subscribe-validating-feed') :
-          GetNewsBlogStringBundle().GetStringFromName('newsblog-getNewMsgsCheck'));
-      }
-    }
-  },
-
-  downloaded: function(feed, aErrorCode)
-  {
-    if (this.mSubscribeMode && aErrorCode == kNewsBlogSuccess)
-    {
-      // if we get here...we should always have a folder by now...either
-      // in feed.folder or FeedItems created the folder for us....
-      updateFolderFeedUrl(feed.folder, feed.url, false);        
-      addFeed(feed.url, feed.name, feed.folder); // add feed just adds the feed to the subscription UI and flushes the datasource
-      
-      // Nice touch: select the folder that now contains the newly subscribed feed...this is particularly nice 
-      // if we just finished subscribing to a feed URL that the operating system gave us.
-      this.mMsgWindow.windowCommands.selectFolder(feed.folder.URI);
-    } 
-    else if (feed.folder)
-      feed.folder.msgDatabase = null;
-
-    if (this.mStatusFeedback)
-    {
-      var newsBlogBundle = GetNewsBlogStringBundle();
-      if (aErrorCode == kNewsBlogNoNewItems)
-        this.mStatusFeedback.showStatusString(newsBlogBundle.GetStringFromName("newsblog-noNewArticlesForFeed"));
-      else if (aErrorCode == kNewsBlogInvalidFeed)
-        this.mStatusFeedback.showStatusString(
-          newsBlogBundle.formatStringFromName("newsblog-feedNotValid", [feed.url], 1));
-      else if (aErrorCode == kNewsBlogRequestFailure)
-        this.mStatusFeedback.showStatusString(newsBlogBundle.formatStringFromName("newsblog-networkError",
-                                              [feed.url], 1));                                           
-      this.mStatusFeedback.stopMeteors();
-    }
-
-    if (!--gNumPendingFeedDownloads)
-    {
-      this.mFeeds = {};
-
-      this.mSubscribeMode = false;
-
-      // should we do this on a timer so the text sticks around for a little while? 
-      // It doesnt look like we do it on a timer for newsgroups so we'll follow that model.
-      if (aErrorCode == kNewsBlogSuccess && this.mStatusFeedback) // don't clear the status text if we just dumped an error to the status bar!
-        this.mStatusFeedback.showStatusString("");
-    }
-  },
-
-  // this gets called after the RSS parser finishes storing a feed item to disk
-  // aCurrentFeedItems is an integer corresponding to how many feed items have been downloaded so far
-  // aMaxFeedItems is an integer corresponding to the total number of feed items to download
-  onFeedItemStored: function (feed, aCurrentFeedItems, aMaxFeedItems)
-  { 
-    // we currently don't do anything here. Eventually we may add
-    // status text about the number of new feed articles received.
-
-    if (this.mSubscribeMode && this.mStatusFeedback) // if we are subscribing to a feed, show feed download progress
-    {
-      this.mStatusFeedback.showStatusString(
-        GetNewsBlogStringBundle().formatStringFromName("subscribe-gettingFeedItems",
-                                                       [aCurrentFeedItems, aMaxFeedItems], 2));
-      this.onProgress(feed, aCurrentFeedItems, aMaxFeedItems);
-    }
-  },
-
-  onProgress: function(feed, aProgress, aProgressMax)
-  {
-    if (feed.url in this.mFeeds) // have we already seen this feed?
-      this.mFeeds[feed.url].currentProgress = aProgress;
-    else
-      this.mFeeds[feed.url] = {currentProgress: aProgress, maxProgress: aProgressMax};
-    
-    this.updateProgressBar();
-  },
-
-  updateProgressBar: function()
-  {
-    var currentProgress = 0;
-    var maxProgress = 0;
-    for (let index in this.mFeeds)
-    {
-      currentProgress += this.mFeeds[index].currentProgress;
-      maxProgress += this.mFeeds[index].maxProgress;
-    }
-
-    // if we start seeing weird "jumping" behavior where the progress bar goes below a threshold then above it again,
-    // then we can factor a fudge factor here based on the number of feeds that have not reported yet and the avg
-    // progress we've already received for existing feeds. Fortunately the progressmeter is on a timer
-    // and only updates every so often. For the most part all of our request have initial progress
-    // before the UI actually picks up a progress value. 
-
-    if (this.mStatusFeedback)
-    {
-      var progress = (currentProgress * 100) / maxProgress;
-      this.mStatusFeedback.showProgress(progress);
-    }
-  }
-}
-
-function GetNewsBlogStringBundle(name)
-{
-  var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(); 
-  strBundleService = strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
-  var strBundle = strBundleService.createBundle("chrome://messenger-newsblog/locale/newsblog.properties"); 
-  return strBundle;
-}
--- a/mailnews/local/src/nsRssIncomingServer.cpp
+++ b/mailnews/local/src/nsRssIncomingServer.cpp
@@ -146,60 +146,29 @@ NS_IMETHODIMP nsRssIncomingServer::SetFl
   NS_ENSURE_SUCCESS(rv, rv);
 
   localFolder->SetFlagsOnDefaultMailboxes(nsMsgFolderFlags::Trash);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsRssIncomingServer::PerformBiff(nsIMsgWindow *aMsgWindow)
 {
-  // do we need to do anything here besides download articles
-  // for each feed? I don't think we have a way to check a feed for new articles without actually
-  // getting the articles. Do we need to SetPerformingBiff to true for this server?
-  nsresult rv;
+  // Get the account root (server) folder and pass it on.
   nsCOMPtr<nsIMsgFolder> rootRSSFolder;
   GetRootMsgFolder(getter_AddRefs(rootRSSFolder));
-
-  // enumerate over the RSS folders and ping each one
-  nsCOMPtr<nsISupportsArray> allDescendents;
-  NS_NewISupportsArray(getter_AddRefs(allDescendents));
-  rv = rootRSSFolder->ListDescendents(allDescendents);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  PRUint32 cnt =0;
-  allDescendents->Count(&cnt);
-
-  nsCOMPtr<nsIUrlListener> urlListener;
-  nsCOMPtr<nsIMsgFolder> rssFolder;
-
-  for (PRUint32 index = 0; index < cnt; index++)
-  {
-    rssFolder = do_QueryElementAt(allDescendents, index);
-    if (rssFolder)
-    {
-      urlListener = do_QueryInterface(rssFolder);
-      // WARNING: Never call GetNewMail with the root folder or you will trigger an infinite loop...
-      GetNewMail(aMsgWindow, urlListener, rssFolder, nsnull);
-    }
-  }
-
+  nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(rootRSSFolder);
+  GetNewMail(aMsgWindow, urlListener, rootRSSFolder, nsnull);
   return NS_OK;
 }
 
-NS_IMETHODIMP nsRssIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener, nsIMsgFolder *aFolder, nsIURI **_retval)
+NS_IMETHODIMP nsRssIncomingServer::GetNewMail(nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener,
+                                              nsIMsgFolder *aFolder, nsIURI **_retval)
 {
+  // Pass the selected folder on to the downloader.
   NS_ENSURE_ARG_POINTER(aFolder);
-
-  // before we even try to get New Mail, check to see if the passed in folder was the root folder.
-  // If it was, then call PerformBiff which will properly walk through each RSS folder, asking it to check for new Mail.
-  bool rootFolder = false;
-  aFolder->GetIsServer(&rootFolder);
-  if (rootFolder)
-    return PerformBiff(aMsgWindow);
-
   nsresult rv;
   nsCOMPtr <nsINewsBlogFeedDownloader> rssDownloader = do_GetService("@mozilla.org/newsblog-feed-downloader;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   rssDownloader->DownloadFeed(nsnull, aFolder, nsnull, nsnull, aUrlListener, aMsgWindow);
   return NS_OK;
 }
 
 NS_IMETHODIMP nsRssIncomingServer::GetAccountManagerChrome(nsAString& aResult)