Bug 737115 - Remove deprecated dnd methods from feed subscribe dialog. Several enhancements: 1.) Remove COM interfaces and nsDragAndDrop in favor of getting everything from the event.dataTransfer. 2.) Allow copy of feeds to other accounts. 3.) Try harder to get valid urls; support dnd from chrome/safari links (ie8/9 dnd doesn't have the info in dt though) for both folderpane and dialog. r=dbienvenu
authoralta88 <alta88@gmail.com>
Wed, 21 Mar 2012 10:57:36 -0600
changeset 9776 43c6ccec3b1e0f2b0c263287e51caa0ee133c906
parent 9775 3aecafffd8e9be73bf1f3d2c585275695db85796
child 9777 dcf4e1f6c78dc95b32e3853c7f0aa7067471ce34
push id7457
push userryanvm@gmail.com
push dateThu, 29 Mar 2012 00:32:42 +0000
treeherdercomm-central@c604f2ff2691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbienvenu
bugs737115
Bug 737115 - Remove deprecated dnd methods from feed subscribe dialog. Several enhancements: 1.) Remove COM interfaces and nsDragAndDrop in favor of getting everything from the event.dataTransfer. 2.) Allow copy of feeds to other accounts. 3.) Try harder to get valid urls; support dnd from chrome/safari links (ie8/9 dnd doesn't have the info in dt though) for both folderpane and dialog. r=dbienvenu
mail/base/content/folderPane.js
mail/base/content/messenger.xul
mail/locales/en-US/chrome/messenger-newsblog/newsblog.properties
mailnews/extensions/newsblog/content/feed-subscriptions.js
mailnews/extensions/newsblog/content/feed-subscriptions.xul
mailnews/extensions/newsblog/content/utils.js
suite/locales/en-US/chrome/mailnews/newsblog/newsblog.properties
--- a/mail/base/content/folderPane.js
+++ b/mail/base/content/folderPane.js
@@ -603,20 +603,19 @@ let gFolderTreeView = {
         // after item before.
         let row = aRow + aOrientation;
         if (row in gFolderTreeView._rowMap &&
             (gFolderTreeView._rowMap[row]._folder == folder))
           return false;
       }
       return true;
     }
-    // allow subscribing to feeds by dragging an url to a feed account
-    else if (Array.indexOf(types, "text/x-moz-url") != -1 &&
-             targetFolder.server.type == "rss")
-      return true;
+    // Allow subscribing to feeds by dragging an url to a feed account.
+    else if (targetFolder.server.type == "rss" && dt.mozItemCount == 1)
+      return FeedUtils.getFeedUriFromDataTransfer(dt) ? true : false;
     else if (Array.indexOf(types, "application/x-moz-file") != -1) {
       if (aOrientation != Ci.nsITreeView.DROP_ON)
         return false;
       // Don't allow drop onto server itself.
       if (targetFolder.isServer)
         return false;
       // Don't allow drop into a folder that cannot take messages.
       if (!targetFolder.canFileMessages)
@@ -707,29 +706,25 @@ let gFolderTreeView = {
         if (extFile.isFile()) {
           let len = extFile.leafName.length;
           if (len > 4 && extFile.leafName.substr(len - 4).toLowerCase() == ".eml")
             cs.CopyFileMessage(extFile, targetFolder, null, false, 1, "", null, msgWindow);
         }
       }
     }
 
-    if (Array.indexOf(types, "text/x-moz-url") != -1 && count == 1 &&
-        targetFolder.server.type == "rss") {
+    if (targetFolder.server.type == "rss" && count == 1) {
       // This is a potential rss feed.  A link image as well as link text url
-      // should be handled.  There's only one, so just get the 0th element.
-      let url = dt.mozGetDataAt("text/x-moz-url-data", 0);
-      let uri = Cc["@mozilla.org/network/io-service;1"]
-                   .getService(Ci.nsIIOService).newURI(url, null, null);
-      if (!(uri.schemeIs("http") || uri.schemeIs("https")))
-        return;
+      // should be handled; try to extract a url from non moz apps as well.
+      let validUri = FeedUtils.getFeedUriFromDataTransfer(dt);
 
-      Cc["@mozilla.org/newsblog-feed-downloader;1"]
-         .getService(Ci.nsINewsBlogFeedDownloader)
-         .subscribeToFeed(url, targetFolder, msgWindow);
+      if (validUri)
+        Cc["@mozilla.org/newsblog-feed-downloader;1"]
+           .getService(Ci.nsINewsBlogFeedDownloader)
+           .subscribeToFeed(validUri.spec, targetFolder, msgWindow);
     }
   },
 
   _onDragStart: function ftv_dragStart(aEvent) {
     // Ugh, this is ugly but necessary
     let view = gFolderTreeView;
 
     if (aEvent.originalTarget.localName != "treechildren")
--- a/mail/base/content/messenger.xul
+++ b/mail/base/content/messenger.xul
@@ -127,16 +127,17 @@
 <script type="application/javascript" src="chrome://messenger/content/nsContextMenu.js"/>
 <script type="application/javascript" src="chrome://messenger/content/mailContextMenus.js"/>
 <script type="application/javascript" src="chrome://messenger/content/accountUtils.js"/>
 <script type="application/javascript" src="chrome://messenger/content/folderPane.js"/>
 <script type="application/javascript" src="chrome://messenger/content/phishingDetector.js"/>
 <script type="application/javascript" src="chrome://communicator/content/contentAreaClick.js"/>
 <script type="application/javascript" src="chrome://global/content/nsDragAndDrop.js"/>
 <script type="application/javascript" src="chrome://messenger/content/editContactOverlay.js"/>
+<script type="application/javascript" src="chrome://messenger-newsblog/content/utils.js"/>
 
 
 <!-- move needed functions into a single js file -->
 <script type="application/javascript" src="chrome://messenger/content/threadPane.js"/>
 
 <commandset id="mailCommands">
   <commandset id="mailFileMenuItems"/>
   <commandset id="mailViewMenuItems"/>
--- a/mail/locales/en-US/chrome/messenger-newsblog/newsblog.properties
+++ b/mail/locales/en-US/chrome/messenger-newsblog/newsblog.properties
@@ -1,16 +1,17 @@
 subscribe-validating-feed=Verifying the feed…
 subscribe-cancelSubscription=Are you sure you wish to cancel subscribing to the current feed?
 subscribe-cancelSubscriptionTitle=Subscribing to a Feed…
 subscribe-feedAlreadySubscribed=You already have a subscription for this feed.
 subscribe-errorOpeningFile=Could not open the file.
 subscribe-feedAdded=Feed added.
 subscribe-feedUpdated=Feed updated.
 subscribe-feedMoved=Feed subscription moved.
+subscribe-feedCopied=Feed subscription copied.
 subscribe-feedRemoved=Feed unsubscribed.
 subscribe-feedNotValid=The Feed URL is not a valid feed.
 subscribe-networkError=The Feed URL could not be found. Please check the name and try again.
 subscribe-loading=Loading, please wait…
 
 subscribe-OPMLImportTitle=Select OPML file to import
 subscribe-OPMLExportTitle=Export feeds as an OPML file
 ## LOCALIZATION NOTE(subscribe-OPMLExportFileDialogTitle): %S is the brandShortName
--- a/mailnews/extensions/newsblog/content/feed-subscriptions.js
+++ b/mailnews/extensions/newsblog/content/feed-subscriptions.js
@@ -44,16 +44,17 @@ var gFeedSubscriptionsWindow = {
   get mTree() { return document.getElementById("rssSubscriptionsList"); },
 
   mFeedContainers: [],
   mRSSServer     : null,
   mActionMode    : null,
   kSubscribeMode : 1,
   kUpdateMode    : 2,
   kMoveMode      : 3,
+  kCopyMode      : 4,
 
   onLoad: function ()
   {
     // Extract the folder argument.
     let folder;
     if (window.arguments && window.arguments[0].folder)
       folder = window.arguments[0].folder;
 
@@ -324,103 +325,93 @@ var gFeedSubscriptionsWindow = {
     getCellText: function (aRow, aColumn)
     {
       let item = this.getItemAtIndex(aRow);
       return (item && aColumn.id == "folderNameCol") ? item.name : "";
     },
 
     canDrop: function (aRow, aOrientation)
     { 
-      let dropResult = this.extractDragData();
-      return (aOrientation == Ci.nsITreeView.DROP_ON) &&
-             dropResult.canDrop &&
-             (dropResult.url || dropResult.index != this.kRowIndexUndefined);
+      let dropResult = this.extractDragData(aRow);
+      return aOrientation == Ci.nsITreeView.DROP_ON && dropResult.canDrop &&
+             (dropResult.dropUrl || dropResult.dropOnIndex != this.kRowIndexUndefined);
     },
 
     drop: function (aRow, aOrientation)
     {
       let win = gFeedSubscriptionsWindow;
-      let results = this.extractDragData();
+      let results = this.extractDragData(aRow);
       if (!results.canDrop)
         return;
 
       // Preselect the drop folder.
       this.selection.select(aRow);
 
-      if (results.url)
+      if (results.dropUrl)
       {
         // Don't freeze the app that initiated the drop just because we are
         // in a loop waiting for the user to dimisss the add feed dialog.
         setTimeout(function() {
-          win.addFeed(results.url, null, true, null, win.kSubscribeMode);
+          win.addFeed(results.dropUrl, null, true, null, win.kSubscribeMode);
         }, 0);
         let folderItem = this.getItemAtIndex(aRow);
         FeedUtils.log.debug("drop: folder, url - " +
-                            folderItem.folder.name+", "+results.url);
+                            folderItem.folder.name+", "+results.dropUrl);
       }
-      else if (results.index != this.kRowIndexUndefined)
+      else if (results.dropOnIndex != this.kRowIndexUndefined)
       {
-        win.moveFeed(results.index, aRow);
+        win.moveCopyFeed(results.dropOnIndex, aRow, results.dropEffect);
       }
     },
 
     // Helper function for drag and drop.
-    extractDragData: function()
+    extractDragData: function(aRow)
     {
-      let canDrop = false;
-      let urlToDrop, sourceUri;
-      let sourceIndex = this.kRowIndexUndefined;
-      let dragService = Cc["@mozilla.org/widget/dragservice;1"].
-                        getService().QueryInterface(Ci.nsIDragService);
-      let dragSession = dragService.getCurrentSession();
+      let dt = this._currentDataTransfer;
+      let dragDataResults = { canDrop:     false,
+                              dropUrl:     null,
+                              dropOnIndex: this.kRowIndexUndefined,
+                              dropEffect:  dt.dropEffect };
+
+      if (dt.getData("text/x-moz-feed-index"))
+      {
+        // Dragging a feed in the tree.
+        if (this.selection)
+        {
+          dragDataResults.dropOnIndex = this.selection.currentIndex;
 
-      let transfer = Cc["@mozilla.org/widget/transferable;1"].
-                     createInstance(Ci.nsITransferable);
-      transfer.addDataFlavor("text/x-moz-url");
-      transfer.addDataFlavor("text/x-moz-feed-index");
+          let curItem = this.getItemAtIndex(this.selection.currentIndex);
+          let newItem = this.getItemAtIndex(aRow);
+          let curServer = curItem && curItem.parentFolder ?
+                            curItem.parentFolder.server : null;
+          let newServer = newItem && newItem.folder ?
+                            newItem.folder.server : null;
 
-      dragSession.getData(transfer, 0);
-      let dataObj = new Object();
-      let flavor = new Object();
-      let len = new Object();
+          // No copying within the same account and no moving to the account
+          // folder in the same account.
+          if (!(curServer == newServer &&
+                (dragDataResults.dropEffect == "copy" ||
+                 newItem.folder == curItem.parentFolder ||
+                 newItem.folder.isServer)))
+            dragDataResults.canDrop = true;
+        }
+      }
+      else
+      {
+        // Try to get a feed url.
+        let validUri = FeedUtils.getFeedUriFromDataTransfer(dt);
 
-      try {
-        transfer.getAnyTransferData(flavor, dataObj, len);
-      }
-      catch (ex) {
-        return { canDrop: false, url: "" };
+        if (validUri)
+        {
+          dragDataResults.canDrop = true;
+          dragDataResults.dropUrl = validUri.spec;
+        }
       }
 
-      if (dataObj.value)
-      {
-        dataObj = dataObj.value.QueryInterface(Ci.nsISupportsString);
-        // Pull the URL out of the data object.
-        sourceUri = dataObj.data.substring(0, len.value);
-
-        if (flavor.value == "text/x-moz-url")
-        {
-          let uri = Cc["@mozilla.org/network/standard-url;1"].
-                    createInstance(Ci.nsIURI);
-          uri.spec = sourceUri.split("\n")[0];
-
-          if (uri.schemeIs("http") || uri.schemeIs("https"))
-          {
-            urlToDrop = uri.spec;
-            canDrop = true;
-          }
-        }
-        else if (flavor.value == "text/x-moz-feed-index")
-        {
-          if (this.selection)
-            sourceIndex = this.selection.currentIndex;
-          canDrop = true;
-        }
-      }  // if dataObj.value
-
-      return { canDrop: canDrop, url: urlToDrop, index: sourceIndex };
+      return dragDataResults;
     },
 
     getParentIndex: function (aRow)
     {
       let item = this.getItemAtIndex(aRow);
 
       if (item)
       {
@@ -853,20 +844,21 @@ var gFeedSubscriptionsWindow = {
     selectFolderValue.setAttribute("filepath", aFolder.filePath.path);
 
     return editFeed.disabled = false;
   },
 
   onClickSelectFolderValue: function(aEvent)
   {
     let target = aEvent.target;
-    if ((aEvent.button && aEvent.button != 0) ||
-        (aEvent.keyCode && aEvent.keyCode != aEvent.DOM_VK_RETURN) ||
-        (aEvent.button && aEvent.button == 0 &&
-         target.inputField.selectionStart != target.inputField.selectionEnd))
+    if ((("button" in aEvent) &&
+         (aEvent.button != 0 ||
+          aEvent.originalTarget.localName != "div" ||
+          target.selectionStart != target.selectionEnd)) ||
+        (aEvent.keyCode && aEvent.keyCode != aEvent.DOM_VK_RETURN))
       return;
 
     // Toggle between showing prettyPath and absolute filePath.
     if (target.getAttribute("showfilepath") == "true")
     {
       target.setAttribute("showfilepath", false);
       target.value = target.getAttribute("prettypath");
     }
@@ -923,21 +915,21 @@ var gFeedSubscriptionsWindow = {
 
   onSelect: function ()
   {
     let item = this.mView.currentItem;
     let isServer = item && item.folder && item.folder.isServer;
     let disable;
     this.updateFeedData(item);
     this.setSummaryFocus();
-    disable = !item || (!item.container || this.mActionMode == this.kMoveMode);
+    disable = !item || !item.container;
     document.getElementById("addFeed").disabled = disable;
-    disable = !item || (item.container && this.mActionMode != this.kMoveMode);
+    disable = !item || item.container;
+    document.getElementById("editFeed").disabled = disable;
     document.getElementById("removeFeed").disabled = disable;
-    document.getElementById("editFeed").disabled = disable;
     document.getElementById("importOPML").disabled = !item || !isServer;
     document.getElementById("exportOPML").disabled = !item || !isServer;
   },
 
   onMouseDown: function (aEvent)
   {
     if (aEvent.button != 0 || aEvent.target.id == "validationText")
       return;
@@ -1186,99 +1178,110 @@ var gFeedSubscriptionsWindow = {
         if (item && item.container && item.url == editFolderURI)
         {
           newParentIndex = index;
           break;
         }
       }
 
       if (newParentIndex != this.mView.kRowIndexUndefined)
-        this.moveFeed(seln.currentIndex, newParentIndex)
-
-      updated = true;
+        this.moveCopyFeed(seln.currentIndex, newParentIndex, "move");
     }
 
     if (!updated)
       return;
 
     ds.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
 
     let message = FeedUtils.strings.GetStringFromName("subscribe-feedUpdated");
     this.updateStatusItem("statusText", message);
   },
 
-  // Moves the feed located at aOldFeedIndex to a child of aNewParentIndex.
-  moveFeed: function(aOldFeedIndex, aNewParentIndex)
+/**
+ * Moves or copies a feed to another folder or account.
+ * 
+ * @param  int aOldFeedIndex    - index in tree of target feed item.
+ * @param  int aNewParentIndex  - index in tree of target parent folder item.
+ * @param  string aMoveCopy     - either "move" or "copy".
+ */
+  moveCopyFeed: function(aOldFeedIndex, aNewParentIndex, aMoveCopy)
   {
+    let moveFeed = aMoveCopy == "move" ? true : false;
     let currentItem = this.mView.getItemAtIndex(aOldFeedIndex);
     if (!currentItem ||
         this.mView.getParentIndex(aOldFeedIndex) == aNewParentIndex)
       // If the new parent is the same as the current parent, then do nothing.
-      return false;
+      return;
 
     let currentParentIndex = this.mView.getParentIndex(aOldFeedIndex);
     let currentParentItem = this.mView.getItemAtIndex(currentParentIndex);
     let currentParentResource = rdf.GetResource(currentParentItem.url);
     let currentFolder = currentParentResource.QueryInterface(Ci.nsIMsgFolder);
 
     let newParentItem = this.mView.getItemAtIndex(aNewParentIndex);
     let newParentResource = rdf.GetResource(newParentItem.url);
     let newFolder = newParentResource.QueryInterface(Ci.nsIMsgFolder);
 
     let ds = getSubscriptionsDS(currentItem.parentFolder.server);
     let resource = rdf.GetResource(currentItem.url);
 
-    let accountMove = false;
+    let accountMoveCopy = false;
     if (currentFolder.rootFolder.URI == newFolder.rootFolder.URI)
     {
       // Moving within the same account/feeds db.
-      if (newFolder.isServer)
-        // No moving to account folder if already in the account.
-        return false;
+      if (newFolder.isServer || !moveFeed)
+        // No moving to account folder if already in the account; can only move,
+        // not copy, to folder in the same account.
+        return;
 
       // Unassert the older URI, add an assertion for the new parent URI.
       ds.Change(resource, FZ_DESTFOLDER,
                 currentParentResource, newParentResource);
       ds.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
       // Update the feed url attributes on the databases for each folder:
       // Remove our feed url property from the current folder.
       updateFolderFeedUrl(currentFolder, currentItem.url, true);
       // Add our feed url property to the new folder.
       updateFolderFeedUrl(newFolder, currentItem.url, false);
     }
     else
     {
-      // Moving to a new account.  If dropping on the account folder, a new
-      // subfolder is created if necessary.
-      accountMove = true;
+      // Moving/copying to a new account.  If dropping on the account folder,
+      // a new subfolder is created if necessary.
+      accountMoveCopy = true;
+      let mode = moveFeed ? this.kMoveMode : this.kCopyMode;
       let params = {quickMode: currentItem.quickMode};
       // Subscribe to the new folder first.  If it already exists in the
       // account or on error, return.
-      if (!this.addFeed(currentItem.url, newFolder, false, params, this.kMoveMode))
+      if (!this.addFeed(currentItem.url, newFolder, false, params, mode))
         return;
       // Unsubscribe the feed from the old folder, if add to the new folder
-      // is successfull.
-      deleteFeed(rdf.GetResource(currentItem.url),
-                 currentItem.parentFolder.server,
-                 currentItem.parentFolder);
+      // is successfull, and doing a move.
+      if (moveFeed)
+        deleteFeed(rdf.GetResource(currentItem.url),
+                   currentItem.parentFolder.server,
+                   currentItem.parentFolder);
     }
 
     // Finally, update our view layer.  Update old parent folder's quickMode
-    // and remove the old row.
-    this.updateFolderQuickModeInView(currentItem, currentParentItem, true);
-    this.mView.removeItemAtIndex(aOldFeedIndex, true);
-    if (aNewParentIndex > aOldFeedIndex)
-      aNewParentIndex--;
+    // and remove the old row, if move.  Otherwise no change to the view.
+    if (moveFeed)
+    {
+      this.updateFolderQuickModeInView(currentItem, currentParentItem, true);
+      this.mView.removeItemAtIndex(aOldFeedIndex, true);
+      if (aNewParentIndex > aOldFeedIndex)
+        aNewParentIndex--;
+    }
 
-    if (accountMove)
+    if (accountMoveCopy)
     {
-      // If a cross account move, download callback will update the view with
-      // the new location.  Preselect folder/mode for callback.
+      // If a cross account move/copy, download callback will update the view
+      // with the new location.  Preselect folder/mode for callback.
       this.selectFolder(newFolder, true, aNewParentIndex);
-      return true;
+      return;
     }
 
     // Add the new row location to the view.
     currentItem.level = newParentItem.level + 1;
     currentItem.parentFolder = newFolder;
     this.updateFolderQuickModeInView(currentItem, newParentItem, false);
     newParentItem.children.push(currentItem);
 
@@ -1316,31 +1319,35 @@ var gFeedSubscriptionsWindow = {
         feed.quickMode = parentItem.quickMode;
         feedItem.quickMode = parentItem.quickMode;
       }
       else
         parentItem.quickMode = feedItem.quickMode;
     }
   },
 
-  beginDrag: function (aEvent)
+  onDragStart: function (aEvent)
   {
     // Get the selected feed article (if there is one).
     let seln = this.mView.selection;
     if (seln.count != 1)
       return;
 
     // Only initiate a drag if the item is a feed (ignore folders/containers).
     let item = this.mView.getItemAtIndex(seln.currentIndex);
     if (!item || item.container)
       return;
 
-    aEvent.dataTransfer.setData("text/x-moz-feed-index",
-                                seln.currentIndex.toString());
-    aEvent.dataTransfer.effectAllowed = "move";
+    aEvent.dataTransfer.setData("text/x-moz-feed-index", seln.currentIndex);
+    aEvent.dataTransfer.effectAllowed = "copyMove";
+  },
+
+  onDragOver: function (aEvent)
+  {
+    this.mView._currentDataTransfer = aEvent.dataTransfer;
   },
 
   mFeedDownloadCallback:
   {
     downloaded: function(feed, aErrorCode)
     {
       // Feed is null if our attempt to parse the feed failed.
       let message = "";
@@ -1409,16 +1416,19 @@ var gFeedSubscriptionsWindow = {
           {
             win.removeFeed(false);
             message = FeedUtils.strings.GetStringFromName(
                         "subscribe-feedUpdated");
           }
           if (win.mActionMode == win.kMoveMode)
             message = FeedUtils.strings.GetStringFromName(
                         "subscribe-feedMoved");
+          if (win.mActionMode == win.kCopyMode)
+            message = FeedUtils.strings.GetStringFromName(
+                        "subscribe-feedCopied");
 
           win.selectFeed(feed, parentIndex);
         }
       }
       else
       {
         // Non success.  Remove intermediate traces from the feeds database.
         if (feed && feed.url && feed.server)
@@ -1433,16 +1443,17 @@ var gFeedSubscriptionsWindow = {
           message = FeedUtils.strings.GetStringFromName(
                       "subscribe-networkError");
 
         if (win.mActionMode != win.kUpdateMode)
           // Re-enable the add button if subscribe failed.
           document.getElementById("addFeed").removeAttribute("disabled");
       }
 
+      win.mActionMode = null;
       win.clearStatusInfo();
       win.updateStatusItem("statusText", message, aErrorCode);
     },
 
     // 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.
@@ -1475,17 +1486,16 @@ var gFeedSubscriptionsWindow = {
     if (aErrorCode == FeedUtils.kNewsBlogInvalidFeed)
       el.removeAttribute("collapsed");
     else
       el.setAttribute("collapsed", true);
   },
 
   clearStatusInfo: function()
   {
-    this.mActionMode = null;
     document.getElementById("statusText").value = "";
     document.getElementById("progressMeter").collapsed = true;
     document.getElementById("validationText").collapsed = true;
   },
 
   checkValidation: function(aEvent)
   {
     if (aEvent.button != 0)
@@ -1524,25 +1534,25 @@ var gFeedSubscriptionsWindow = {
     },
 
     get currentSelectedItem() {
       return this.feedWindow ? this.feedWindow.mView.currentItem : null;
     },
 
     folderAdded: function(aFolder)
     {
-      if (aFolder.server.type != "rss")
+      if (aFolder.server.type != "rss" || FeedUtils.isInTrash(aFolder))
         return;
       FeedUtils.log.debug("folderAdded: folder - "+ aFolder.name);
       this.feedWindow.refreshSubscriptionView();
     },
 
     folderDeleted: function(aFolder)
     {
-      if (aFolder.server.type != "rss")
+      if (aFolder.server.type != "rss" || FeedUtils.isInTrash(aFolder))
         return;
       FeedUtils.log.debug("folderDeleted: folder - "+ aFolder.name);
       let curSelItem = this.currentSelectedItem;
       let feedWindow = this.feedWindow;
       if (curSelItem && curSelItem.container && curSelItem.folder == aFolder)
       {
         let curSelIndex = this.feedWindow.mView.selection.currentIndex;
         this.feedWindow.mView.removeItemAtIndex(curSelIndex);
@@ -1550,17 +1560,17 @@ var gFeedSubscriptionsWindow = {
       else
         setTimeout(function() {
           feedWindow.refreshSubscriptionView();
         }, 20);
     },
 
     folderRenamed: function(aOrigFolder, aNewFolder)
     {
-      if (aNewFolder.server.type != "rss")
+      if (aNewFolder.server.type != "rss" || FeedUtils.isInTrash(aNewFolder))
         return;
       FeedUtils.log.debug("folderRenamed: old:new - "+
                           aOrigFolder.name+":"+aNewFolder.name);
       let curSelItem = this.currentSelectedItem;
       let feedWindow = this.feedWindow;
       setTimeout(function() {
         feedWindow.refreshSubscriptionView();
         if (curSelItem && curSelItem.container &&
--- a/mailnews/extensions/newsblog/content/feed-subscriptions.xul
+++ b/mailnews/extensions/newsblog/content/feed-subscriptions.xul
@@ -68,18 +68,16 @@
           src="chrome://messenger-newsblog/content/feed-subscriptions.js"/>
   <script type="application/javascript"
           src="chrome://messenger-newsblog/content/Feed.js"/>
   <script type="application/javascript"
           src="chrome://messenger-newsblog/content/FeedItem.js"/>
   <script type="application/javascript"
           src="chrome://messenger-newsblog/content/feed-parser.js"/>
   <script type="application/javascript"
-          src="chrome://global/content/nsDragAndDrop.js"/>
-  <script type="application/javascript"
           src="chrome://messenger/content/widgetglue.js"/>
 
   <keyset id="extensionsKeys">
     <key id="key_close"
          key="&cmd.close.commandKey;"
          modifiers="accel"
          oncommand="window.close();"/>
     <key id="key_close2"
@@ -105,17 +103,18 @@
           seltype="single">
       <treecols>
         <treecol id="folderNameCol"
                  flex="2"
                  primary="true"
                  hideheader="true"/>
       </treecols>
       <treechildren id="subscriptionChildren"
-                    ondraggesture="gFeedSubscriptionsWindow.beginDrag(event);"/>
+                    ondragstart="gFeedSubscriptionsWindow.onDragStart(event);"
+                    ondragover="gFeedSubscriptionsWindow.onDragOver(event);"/>
     </tree>
 
     <hbox id="rssFeedInfoBox">
       <vbox flex="1">
         <grid flex="1">
           <columns>
             <column/>
             <column flex="1"/>
--- a/mailnews/extensions/newsblog/content/utils.js
+++ b/mailnews/extensions/newsblog/content/utils.js
@@ -55,16 +55,94 @@ var FeedUtils = {
   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,
 
 /**
+ * Dragging something from somewhere.  It may be a nice x-moz-url or from a
+ * browser or app that provides a less nice dataTransfer object in the event.
+ * Extract the url and if it passes the scheme test, try to subscribe.
+ * 
+ * @param  nsIDOMDataTransfer aDataTransfer  - the dnd event's dataTransfer.
+ * @return nsIURI uri                        - a uri if valid, null if none.
+ */
+  getFeedUriFromDataTransfer: function(aDataTransfer) {
+    let dt = aDataTransfer;
+    let types = ["text/x-moz-url-data", "text/x-moz-url"];
+    let validUri = false;
+    let uri = Cc["@mozilla.org/network/standard-url;1"].
+              createInstance(Ci.nsIURI);
+
+    if (dt.getData(types[0]))
+    {
+      // The url is the data.
+      uri.spec = dt.mozGetDataAt(types[0], 0);
+      validUri = this.isValidScheme(uri);
+      FeedUtils.log.trace("getFeedUriFromDataTransfer: dropEffect:type:value - "+
+                          dt.dropEffect+" : "+types[0]+" : "+uri.spec);
+    }
+    else if (dt.getData(types[1]))
+    {
+      // The url is the first part of the data, the second part is random.
+      uri.spec = dt.mozGetDataAt(types[1], 0).split("\n")[0];
+      validUri = this.isValidScheme(uri);
+      FeedUtils.log.trace("getFeedUriFromDataTransfer: dropEffect:type:value - "+
+                          dt.dropEffect+" : "+types[0]+" : "+uri.spec);
+    }
+    else
+    {
+      // Go through the types and see if there's a url; get the first one.
+      for (let i = 0; i < dt.types.length; i++) {
+        let spec = dt.mozGetDataAt(dt.types[i], 0);
+        FeedUtils.log.trace("getFeedUriFromDataTransfer: dropEffect:index:type:value - "+
+                            dt.dropEffect+" : "+i+" : "+dt.types[i]+" : "+spec);
+        try {
+          uri.spec = spec;
+          validUri = this.isValidScheme(uri);
+        }
+        catch(ex) {}
+
+        if (validUri)
+          break;
+      };
+    }
+
+    return validUri ? uri : null;
+  },
+
+/**
+ * Returns if a uri is valid to subscribe.
+ * 
+ * @param  nsIURI aUri  - the Uri.
+ * @return boolean      - true if a valid scheme, false if not.
+ */
+  isValidScheme: function(aUri) {
+    return (aUri instanceof Ci.nsIURI) &&
+           (aUri.schemeIs("http") || aUri.schemeIs("https"));
+  },
+
+/**
+ * Is a folder Trash or in Trash.
+ * 
+ * @param  nsIMsgFolder aFolder   - the folder.
+ * @return boolean                - true if folder is Trash else false.
+ */
+  isInTrash: function(aFolder) {
+    let trashFolder =
+        aFolder.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+    if (trashFolder &&
+        (trashFolder == aFolder || trashFolder.isAncestorOf(aFolder)))
+      return true;
+    return false;
+  },
+
+/**
  * Return a folder path string constructed from individual folder UTF8 names
  * stored as properties (not possible hashes used to construct disk foldername).
  * 
  * @param  nsIMsgFolder aFolder     - the folder.
  * @return string prettyName | null - name or null if not a disk folder.
  */
   getFolderPrettyPath: function(aFolder) {
     let msgFolder = GetMsgFolderFromUri(aFolder.URI, true);
--- a/suite/locales/en-US/chrome/mailnews/newsblog/newsblog.properties
+++ b/suite/locales/en-US/chrome/mailnews/newsblog/newsblog.properties
@@ -1,16 +1,17 @@
 subscribe-validating-feed=Verifying the feed…
 subscribe-cancelSubscription=Are you sure you wish to cancel subscribing to the current feed?
 subscribe-cancelSubscriptionTitle=Subscribing to a Feed…
 subscribe-feedAlreadySubscribed=You already have a subscription for this feed.
 subscribe-errorOpeningFile=Could not open the file.
 subscribe-feedAdded=Feed added.
 subscribe-feedUpdated=Feed updated.
 subscribe-feedMoved=Feed subscription moved.
+subscribe-feedCopied=Feed subscription copied.
 subscribe-feedRemoved=Feed unsubscribed.
 subscribe-feedNotValid=The Feed URL is not a valid feed.
 subscribe-networkError=The Feed URL could not be found. Please check the name and try again.
 subscribe-loading=Loading, please wait…
 
 subscribe-OPMLImportTitle=Select OPML file to import
 subscribe-OPMLExportTitle=Export feeds as an OPML file
 ## LOCALIZATION NOTE(subscribe-OPMLExportFileDialogTitle): %S is the brandShortName