Bug 415372 - Implement Feed Preview. r+sr=Neil.
authorCaio Tiago Oliveira <asrail@gmail.com>
Thu, 30 Apr 2009 12:47:26 +0200
changeset 2517 3c908fd10922
parent 2516 8e72da555191
child 2518 82c4914ad8ea
push id2053
push userstefanh@inbox.com
push dateThu, 30 Apr 2009 10:47:36 +0000
treeherdercomm-central@3c908fd10922 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs415372
Bug 415372 - Implement Feed Preview. r+sr=Neil.
suite/Makefile.in
suite/browser/linkToolbarOverlay.xul
suite/browser/navigator.xul
suite/browser/navigatorOverlay.xul
suite/browser/pageinfo/feeds.js
suite/browser/pageinfo/feeds.xml
suite/build/Makefile.in
suite/build/nsSuiteModule.cpp
suite/common/feeds/subscribe.js
suite/common/feeds/subscribe.xhtml
suite/common/jar.mn
suite/common/pref/pref-applications.js
suite/common/utilityOverlay.js
suite/feeds/public/Makefile.in
suite/feeds/public/nsIFeedResultService.idl
suite/feeds/public/nsIFeedWriter.idl
suite/feeds/public/nsIWebContentConverterRegistrar.idl
suite/feeds/src/FeedConverter.js
suite/feeds/src/FeedWriter.js
suite/feeds/src/Makefile.in
suite/feeds/src/WebContentConverter.js
suite/feeds/src/nsAboutFeeds.js
suite/feeds/src/nsFeedSniffer.cpp
suite/feeds/src/nsFeedSniffer.h
suite/installer/unix/packages
suite/installer/windows/packages
suite/locales/en-US/chrome/common/feeds/subscribe.dtd
suite/locales/en-US/chrome/common/feeds/subscribe.properties
suite/locales/en-US/chrome/common/pref/pref-applications.properties
suite/locales/jar.mn
suite/makefiles.sh
suite/themes/classic/communicator/feed-subscribe.css
suite/themes/classic/communicator/icons/feedIcon.png
suite/themes/classic/jar.mn
suite/themes/modern/communicator/feed-subscribe.css
suite/themes/modern/communicator/icons/feedIcon.png
suite/themes/modern/jar.mn
--- a/suite/Makefile.in
+++ b/suite/Makefile.in
@@ -45,16 +45,18 @@ include $(DEPTH)/config/autoconf.mk
 
 # XXX Disable for release builds
 PARALLEL_DIRS = debugQA
 
 PARALLEL_DIRS	+= \
 		branding \
 		browser \
 		common \
+		feeds/public \
+		feeds/src \
 		locales \
 		modules \
 		themes/classic \
 		themes/modern \
 		profile \
 		security \
 		shell/public \
 		$(NULL)
--- a/suite/browser/linkToolbarOverlay.xul
+++ b/suite/browser/linkToolbarOverlay.xul
@@ -194,14 +194,13 @@
 
         </menupopup>
       </toolbarbutton>
 
       <toolbarbutton id="link-feed" class="bookmark-item"
           type="menu"
           container="true"
           label="&feedButton.label;" disabled="true">
-        <!-- XXXCallek oncommand temporary until feed Preview works -->
-        <menupopup id="link-feed-popup" oncommand="event.stopPropagation(); return subscribeToFeed('feed:' + event.target.getAttribute('href');"/>
+        <menupopup id="link-feed-popup"/>
       </toolbarbutton>
     </toolbar>
   </toolbox>
 </overlay>
--- a/suite/browser/navigator.xul
+++ b/suite/browser/navigator.xul
@@ -137,20 +137,20 @@
 
   <popupset id="mainPopupSet"/> <!-- Firefox extension compatibility -->
 
   <popup id="toolbar-context-menu"/>
 
   <!-- context menu -->
   <popupset id="contentAreaContextSet"/>
 
-  <!-- XXXCallek this should be loadURI rather than subscribe* here -->
   <popup id="feedsPopup" popupanchor="bottomright" popupalign="topright"
          onpopupshowing="window.XULBrowserWindow.populateFeeds(this);"
-         oncommand="subscribeToFeed('feed:' + event.target.statusText /*, gBrowser.currentURI*/);"/>
+         oncommand="subscribeToFeed(event.target.statusText, event);"
+         onclick="checkForMiddleClick(this, event);"/>
   <panel type="autocomplete" id="PopupAutoComplete" level="top"/>
 
   <toolbox id="navigator-toolbox" class="toolbox-top" deferattached="true"
            mode="full" defaultmode="full">
     <!-- Menu -->
     <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar"
              persist="collapsed" grippytooltiptext="&menuBar.tooltip;" customizable="true"
              defaultset="menubar-items"
--- a/suite/browser/navigatorOverlay.xul
+++ b/suite/browser/navigatorOverlay.xul
@@ -482,17 +482,18 @@
         <menuitem key="addBookmarkKb"   command="Browser:AddBookmark"/>
         <menuitem key="addBookmarkAsKb" command="Browser:AddBookmarkAs"/>
         <menuitem                       command="Browser:AddGroupmarkAs"/>
         <menuitem key="manBookmarkKb"   command="Browser:ManageBookmark"/>
         <menuseparator/>
         <menu id="feedsMenu" class="menu-iconic" disabled="true"
               label="&feedsMenu.label;" accesskey="&feedsMenu.accesskey;">
           <menupopup onpopupshowing="window.XULBrowserWindow.populateFeeds(this);"
-                     oncommand="subscribeToFeed('feed:' + event.target.statusText /*, gBrowser.currentURI*/);"/>
+                     oncommand="subscribeToFeed(event.target.statusText, event);"
+                     onclick="checkForMiddleClick(this, event);"/>
         </menu>
         <menuseparator/>
       </menupopup>
     </menu>
       
   <menu id="tasksMenu">
     <menupopup id="taskPopup">
       <menuitem id="menu_searchWeb" label="&searchInternetCmd.label;" key="searchInternetKb" accesskey="&searchInternetCmd.accesskey;" command="Browser:SearchInternet"/>
--- a/suite/browser/pageinfo/feeds.js
+++ b/suite/browser/pageinfo/feeds.js
@@ -68,20 +68,20 @@ function initFeedTab()
         addRow(link.title, type, link.href);
       }
     }
   }
   var feedListbox = document.getElementById("feedListbox");
   document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
 }
 
-function onSubscribeFeed()
+function onSubscribeFeed(event)
 {
   var listbox = document.getElementById("feedListbox");
-  subscribeToFeed('feed:' + listbox.selectedItem.getAttribute("feedURL"));
+  subscribeToFeed(listbox.selectedItem.getAttribute("feedURL"), event);
 }
 
 function addRow(name, type, url)
 {
   var item = document.createElement("richlistitem");
   item.setAttribute("feed", "true");
   item.setAttribute("name", name);
   item.setAttribute("type", type);
--- a/suite/browser/pageinfo/feeds.xml
+++ b/suite/browser/pageinfo/feeds.xml
@@ -54,14 +54,14 @@
           <xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
                        class="feedTitle"/>
           <xul:label xbl:inherits="value=type"/>
         </xul:hbox>
         <xul:textbox xbl:inherits="value=feedURL" readonly="true"/>
         <xul:hbox flex="1" class="feed-subscribe">
           <xul:spacer flex="1"/>
           <xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
-                      oncommand="onSubscribeFeed()"/>
+                      oncommand="onSubscribeFeed(event)"/>
         </xul:hbox>
       </xul:vbox>
     </content>
   </binding>
 </bindings>
--- a/suite/build/Makefile.in
+++ b/suite/build/Makefile.in
@@ -77,22 +77,24 @@ ifeq ($(OS_ARCH),WINNT)
 OS_LIBS += $(call EXPAND_LIBNAME,ole32 shell32)
 endif
 
 LOCAL_INCLUDES += \
 		-I$(topsrcdir)/suite/profile \
 		-I$(topsrcdir)/suite/profile/migration/src \
 		-I$(topsrcdir)/suite/browser/src \
 		-I$(topsrcdir)/suite/shell/src \
+		-I$(topsrcdir)/suite/feeds/src \
 		$(NULL)
 
 SHARED_LIBRARY_LIBS = \
 	../profile/$(LIB_PREFIX)suiteprofile_s.$(LIB_SUFFIX) \
 	../profile/migration/src/$(LIB_PREFIX)suitemigration_s.$(LIB_SUFFIX) \
 	../browser/src/$(LIB_PREFIX)suitebrowser_s.$(LIB_SUFFIX) \
+	../feeds/src/$(LIB_PREFIX)suitefeeds_s.$(LIB_SUFFIX) \
 	$(NULL)
 
 ifeq ($(OS_ARCH),WINNT)
 SHARED_LIBRARY_LIBS += ../shell/src/$(LIB_PREFIX)shellservice_s.$(LIB_SUFFIX)
 endif
 
 EXTRA_DSO_LDOPTS += \
 	$(LIBS_DIR) \
--- a/suite/build/nsSuiteModule.cpp
+++ b/suite/build/nsSuiteModule.cpp
@@ -41,16 +41,17 @@
 #include "nsProfileMigrator.h"
 #include "nsSeamonkeyProfileMigrator.h"
 #include "nsThunderbirdProfileMigrator.h"
 #include "nsInternetSearchService.h"
 #include "nsLocalSearchService.h"
 #include "nsIGenericFactory.h"
 #include "nsRDFCID.h"
 #include "nsBookmarksService.h"
+#include "nsFeedSniffer.h"
 
 #if defined(XP_WIN)
 #include "nsUrlWidget.h"
 #include "nsWindowsShellService.h"
 #endif
 
 /////////////////////////////////////////////////////////////////////////////
 
@@ -60,16 +61,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWi
 #endif // Windows
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSuiteDirectoryProvider)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsProfileMigrator)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSeamonkeyProfileMigrator)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsThunderbirdProfileMigrator)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(LocalSearchDataSource, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(InternetSearchDataSource, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsBookmarksService, Init)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer)
 
 /////////////////////////////////////////////////////////////////////////////
 
 static const nsModuleComponentInfo components[] = {
 #ifdef XP_WIN
   { NS_IURLWIDGET_CLASSNAME, NS_IURLWIDGET_CID,
     NS_IURLWIDGET_CONTRACTID, nsUrlWidgetConstructor },
   { "SeaMonkey Windows Integration",
@@ -128,12 +130,18 @@ static const nsModuleComponentInfo compo
   { "Bookmarks",
     NS_BOOKMARKS_SERVICE_CID,
     "@mozilla.org/embeddor.implemented/bookmark-charset-resolver;1",
     nsBookmarksServiceConstructor },
 
   { "Bookmarks",
     NS_BOOKMARKS_SERVICE_CID,
     NS_BOOKMARKS_DATASOURCE_CONTRACTID,
-    nsBookmarksServiceConstructor }
+    nsBookmarksServiceConstructor },
+
+  { "Feed Sniffer",
+    NS_FEEDSNIFFER_CID,
+    NS_FEEDSNIFFER_CONTRACTID,
+    nsFeedSnifferConstructor,
+    nsFeedSniffer::Register }
 };
 
 NS_IMPL_NSGETMODULE(SuiteModule, components)
new file mode 100644
--- /dev/null
+++ b/suite/common/feeds/subscribe.js
@@ -0,0 +1,61 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Subscribe Handler.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Asaf Romano <mano@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 SubscribeHandler = {
+  /**
+   * The nsIFeedWriter object that produces the UI
+   */
+  _feedWriter: null,
+
+  init: function SH_init() {
+    this._feedWriter = new BrowserFeedWriter();
+    this._feedWriter.init(window);
+  },
+
+  writeContent: function SH_writeContent() {
+    this._feedWriter.writeContent();
+  },
+
+  uninit: function SH_uninit() {
+    this._feedWriter.close();
+  },
+
+  subscribe: function FH_subscribe() {
+    this._feedWriter.subscribe();
+  }
+};
new file mode 100644
--- /dev/null
+++ b/suite/common/feeds/subscribe.xhtml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD
+    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+    "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % globalDTD
+    SYSTEM "chrome://global/locale/global.dtd">
+  %globalDTD;
+  <!ENTITY % feedDTD
+    SYSTEM "chrome://communicator/locale/feeds/subscribe.dtd">
+  %feedDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<html id="feedHandler"
+      xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <head>
+    <title>&feedPage.title;</title>
+    <link rel="stylesheet"
+          href="chrome://communicator/skin/feed-subscribe.css"
+          type="text/css"
+          media="all"/>
+    <script type="application/x-javascript"
+            src="chrome://communicator/content/feeds/subscribe.js"/>
+  </head>
+  <body onload="SubscribeHandler.writeContent();" onunload="SubscribeHandler.uninit();">
+    <div id="feedHeaderContainer">
+      <div id="feedHeader" dir="&locale.dir;">
+        <div id="feedIntroText">
+          <p id="feedSubscriptionInfo1" />
+          <p id="feedSubscriptionInfo2" />
+        </div>
+
+<!-- XXXmano this can't have any whitespace in it.  Otherwise you would see
+     how much XUL-in-XHTML sucks, see bug 348830 -->
+        <div id="feedSubscribeLine"
+          ><xul:vbox
+            ><xul:hbox align="center"
+              ><xul:description id="subscribeUsingDescription"
+              /><xul:menulist id="handlersMenuList" aria-labelledby="subscribeUsingDescription"
+                ><xul:menupopup menugenerated="true" id="handlersMenuPopup"
+                  ><xul:menuitem id="messengerFeedsMenuItem" label="&feedMessenger;" class="menuitem-iconic" image="chrome://communicator/skin/icons/feedIcon16.png" selected="true"
+                  /><xul:menuseparator
+                /></xul:menupopup
+              ></xul:menulist
+            ></xul:hbox
+            ><xul:hbox
+              ><xul:checkbox id="alwaysUse" checked="false"
+            /></xul:hbox
+            ><xul:hbox align="center"
+              ><xul:spacer flex="1"
+              /><xul:button label="&feedSubscribeNow;" id="subscribeButton"
+            /></xul:hbox
+          ></xul:vbox
+        ></div
+      ></div>
+    </div>
+
+    <script type="application/x-javascript">
+      SubscribeHandler.init();
+    </script>
+
+    <div id="feedBody">
+      <div id="feedTitle">
+        <a id="feedTitleLink">
+          <img id="feedTitleImage"/>
+        </a>
+        <div id="feedTitleContainer">
+          <h1 id="feedTitleText"/>
+          <h2 id="feedSubtitleText"/>
+        </div>
+      </div>
+      <div id="feedContent"/>
+    </div>
+  </body>
+</html>
--- a/suite/common/jar.mn
+++ b/suite/common/jar.mn
@@ -122,16 +122,18 @@ comm.jar:
    content/communicator/bookmarks/bookmarksTree.xml                 (bookmarks/bookmarksTree.xml)
    content/communicator/bookmarks/findBookmark.js                   (bookmarks/findBookmark.js)
    content/communicator/bookmarks/findBookmark.xul                  (bookmarks/findBookmark.xul)
    content/communicator/bookmarks/sortFolder.js                     (bookmarks/sortFolder.js)
    content/communicator/bookmarks/sortFolder.xul                    (bookmarks/sortFolder.xul)
    content/communicator/directory/directory.html                    (directory/directory.html)
    content/communicator/directory/directory.js                      (directory/directory.js)
    content/communicator/directory/directory.xul                     (directory/directory.xul)
+   content/communicator/feeds/subscribe.xhtml                       (feeds/subscribe.xhtml)
+   content/communicator/feeds/subscribe.js                          (feeds/subscribe.js)
    content/communicator/history/controller.js                       (history/controller.js)
    content/communicator/history/history.js                          (history/history.js)
    content/communicator/history/history.xul                         (history/history.xul)
    content/communicator/history/history-panel.xul                   (history/history-panel.xul)
    content/communicator/history/places.css                          (history/places.css)
    content/communicator/history/placesOverlay.xul                   (history/placesOverlay.xul)
    content/communicator/history/sidebarUtils.js                     (history/sidebarUtils.js)
    content/communicator/history/tree.xml                            (history/tree.xml)
--- a/suite/common/pref/pref-applications.js
+++ b/suite/common/pref/pref-applications.js
@@ -51,37 +51,34 @@ function Startup()
 //****************************************************************************//
 // Constants & Enumeration Values
 
 // constants for interfaces we need multiple times
 const nsIHandlerApp = Components.interfaces.nsIHandlerApp;
 const nsIHandlerInfo = Components.interfaces.nsIHandlerInfo;
 const nsILocalHandlerApp = Components.interfaces.nsILocalHandlerApp;
 const nsIWebHandlerApp = Components.interfaces.nsIWebHandlerApp;
-// XXX-SeaMonkey: remove comment once we implement nsIWebContentHandlerInfo
-//const nsIWebContentHandlerInfo = Components.interfaces.nsIWebContentHandlerInfo;
+const nsIWebContentHandlerInfo = Components.interfaces.nsIWebContentHandlerInfo;
 const nsIFilePicker = Components.interfaces.nsIFilePicker;
 const nsIMIMEInfo = Components.interfaces.nsIMIMEInfo;
 const nsIPropertyBag = Components.interfaces.nsIPropertyBag;
 
 // global services
 var handlerSvc = Components.classes["@mozilla.org/uriloader/handler-service;1"]
                            .getService(Components.interfaces.nsIHandlerService);
 var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
                         .getService(Components.interfaces.nsIPrefBranch2);
 var categoryMgr = Components.classes["@mozilla.org/categorymanager;1"]
                             .getService(Components.interfaces.nsICategoryManager);
 var mimeSvc = Components.classes["@mozilla.org/mime;1"]
                         .getService(Components.interfaces.nsIMIMEService);
 var ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
                       .getService(Components.interfaces.nsIIOService);
-// XXX-SeaMonkey: remove null and comment once we implement nsIWebContentConverterService
-// var converterSvc = Components.classes["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]
-//                              .getService(Components.interfaces.nsIWebContentConverterService);
-var converterSvc = null;
+var converterSvc = Components.classes["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]
+                             .getService(Components.interfaces.nsIWebContentConverterService);
 #ifdef HAVE_SHELL_SERVICE
 var shellSvc = Components.classes["@mozilla.org/suite/shell-service;1"]
                          .getService(Components.interfaces.nsIShellService);
 #else
 var shellSvc = null;
 #endif
 
 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
@@ -94,23 +91,23 @@ const PREF_DISABLED_PLUGIN_TYPES = "plug
 const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
 const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
   "browser.download.hide_plugins_without_extensions";
 
 /*
  * Preferences where we store handling information about the feed type.
  *
  * browser.feeds.handler
- * - "bookmarks", "reader" (clarified further using the .default preference),
+ * - "messenger", "reader" (clarified further using the .default preference),
  *   or "ask" -- indicates the default handler being used to process feeds;
- *   "bookmarks" is obsolete; to specify that the handler is bookmarks,
- *   set browser.feeds.handler.default to "bookmarks";
+ *   "messenger" is obsolete, use "reader" instead; to specify that the
+ *    handler is messenger, set browser.feeds.handler.default to "messenger";
  *
  * browser.feeds.handler.default
- * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used
+ * - "messenger", "client" or "web" -- indicates the chosen feed reader used
  *   to display feeds, either transiently (i.e., when the "use as default"
  *   checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
  *   or more permanently (i.e., the item displayed in the dropdown in Feeds
  *   preferences)
  *
  * browser.feeds.handler.webservice
  * - the URL of the currently selected web service used to read feeds
  *
@@ -539,43 +536,41 @@ FeedHandlerInfo.prototype = {
         return null;
 
       case "web":
         var uri = document.getElementById(this._prefSelectedWeb).value;
         if (!uri)
           return null;
         return converterSvc.getWebContentHandlerByURI(this.type, uri);
 
-      case "bookmarks":
+      case "messenger":
       default:
-        // When the pref is set to bookmarks, we handle feeds internally,
+        // When the pref is set to messenger, we handle feeds internally,
         // we don't forward them to a local or web handler app, so there is
         // no preferred handler.
         return null;
     }
   },
 
   set preferredApplicationHandler(aNewValue) {
     if (aNewValue instanceof nsILocalHandlerApp) {
       document.getElementById(this._prefSelectedApp).value = aNewValue.executable;
       document.getElementById(this._prefSelectedReader).value = "client";
     }
-    /* XXX-SeaMonkey: remove comment once we implement nsIWebContentHandlerInfo
     else if (aNewValue instanceof nsIWebContentHandlerInfo) {
       document.getElementById(this._prefSelectedWeb).value = aNewValue.uri;
       document.getElementById(this._prefSelectedReader).value = "web";
       // Make the web handler be the new "auto handler" for feeds.
       // Note: we don't have to unregister the auto handler when the user picks
-      // a non-web handler (local app, Live Bookmarks, etc.) because the service
+      // a non-web handler (local app, RSS News & Blogs, etc.) because the service
       // only uses the "auto handler" when the selected reader is a web handler.
       // We also don't have to unregister it when the user turns on "always ask"
       // (i.e. preview in browser), since that also overrides the auto handler.
       converterSvc.setAutoHandler(this.type, aNewValue);
     }
-    */
   },
 
   _possibleApplicationHandlers: null,
 
   get possibleApplicationHandlers() {
     if (this._possibleApplicationHandlers)
       return this._possibleApplicationHandlers;
 
@@ -697,19 +692,16 @@ FeedHandlerInfo.prototype = {
     // Should we instead return null?
     return "";
   },
 
   // What to do with content of this type.
   get preferredAction() {
     switch (document.getElementById(this._prefSelectedAction).value) {
 
-      case "bookmarks":
-        return nsIHandlerInfo.handleInternally;
-
       case "reader": {
         let preferredApp = this.preferredApplicationHandler;
         let defaultApp = this._defaultApplicationHandler;
 
         // If we have a valid preferred app, return useSystemDefault if it's
         // the default app; otherwise return useHelperApp.
         if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
           if (defaultApp && defaultApp.equals(preferredApp))
@@ -724,26 +716,27 @@ FeedHandlerInfo.prototype = {
         // direct the user to live bookmarks.
         return nsIHandlerInfo.handleInternally;
       }
 
       // If the action is "ask", then alwaysAskBeforeHandling will override
       // the action, so it doesn't matter what we say it is, it just has to be
       // something that doesn't cause the controller to hide the type.
       case "ask":
+      case "messenger":
       default:
         return nsIHandlerInfo.handleInternally;
     }
   },
 
   set preferredAction(aNewValue) {
     switch (aNewValue) {
 
       case nsIHandlerInfo.handleInternally:
-        document.getElementById(this._prefSelectedReader).value = "bookmarks";
+        document.getElementById(this._prefSelectedReader).value = "messenger";
         break;
 
       case nsIHandlerInfo.useHelperApp:
         document.getElementById(this._prefSelectedAction).value = "reader";
         // The controller has already set preferredApplicationHandler
         // to the new helper app.
         break;
 
@@ -794,22 +787,20 @@ FeedHandlerInfo.prototype = {
         let pref = document.getElementById(PREF_FEED_SELECTED_APP);
         var preferredAppFile = pref.value;
         if (preferredAppFile) {
           let preferredApp = getLocalHandlerApp(preferredAppFile);
           if (app.equals(preferredApp))
             pref.reset();
         }
       }
-      /* XXX-SeaMonkey: remove comment once we implement nsIWebContentHandlerInfo
       else {
         app.QueryInterface(nsIWebContentHandlerInfo);
         converterSvc.removeContentHandler(app.contentType, app.uri);
       }
-      */
     }
     this._possibleApplicationHandlers._removed = [];
   },
 
 
   //**************************************************************************//
   // Icons
 
@@ -1255,19 +1246,19 @@ var gApplicationsPane = {
       case nsIHandlerInfo.useHelperApp:
         var preferredApp = aHandlerInfo.preferredApplicationHandler;
         var name = (preferredApp instanceof nsILocalHandlerApp) ?
                    getDisplayNameForFile(preferredApp.executable) :
                    preferredApp.name;
         return this._prefsBundle.getFormattedString("useApp", [name]);
 
       case nsIHandlerInfo.handleInternally:
-        // For the feed type, handleInternally means live bookmarks.
-        if (isFeedType(aHandlerInfo.type)) 
-          return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+        // For the feed type, handleInternally means News & Blogs.
+        if (isFeedType(aHandlerInfo.type))
+          return this._prefsBundle.getFormattedString("addNewsBlogsInApp",
                                                       [this._brandShortName]);
 
         // For other types, handleInternally looks like either useHelperApp
         // or useSystemDefault depending on whether or not there's a preferred
         // handler app.
         return (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler)) ?
                aHandlerInfo.preferredApplicationHandler.name :
                aHandlerInfo.defaultDescription;
@@ -1302,20 +1293,18 @@ var gApplicationsPane = {
       return false;
 
     if (aHandlerApp instanceof nsILocalHandlerApp)
       return this._isValidHandlerExecutable(aHandlerApp.executable);
 
     if (aHandlerApp instanceof nsIWebHandlerApp)
       return aHandlerApp.uriTemplate;
 
-    /* XXX-SeaMonkey: remove comment once we implement nsIWebContentHandlerInfo
     if (aHandlerApp instanceof nsIWebContentHandlerInfo)
       return aHandlerApp.uri;
-    */
 
     return false;
   },
 
   _isValidHandlerExecutable: function(aExecutable) {
     return aExecutable &&
            aExecutable.exists() &&
            aExecutable.isExecutable() &&
@@ -1379,23 +1368,22 @@ var gApplicationsPane = {
       saveMenuItem.setAttribute("value", nsIHandlerInfo.saveToDisk);
       let label = this._prefsBundle.getString("saveFile");
       saveMenuItem.setAttribute("label", label);
       saveMenuItem.setAttribute("tooltiptext", label);
       saveMenuItem.setAttribute("appHandlerIcon", "save");
       menuPopup.appendChild(saveMenuItem);
     }
 
-    // If this is the feed type, add a Live Bookmarks item.
-    // XXX-SeaMonkey: remove the | && false| once we support live bookmarks!
-    if (isFeedType(handlerInfo.type) && false) {
+    // If this is the feed type, add a News & Blogs item.
+    if (isFeedType(handlerInfo.type)) {
       let internalMenuItem = document.createElement("menuitem");
       internalMenuItem.setAttribute("class", "handler-action");
       internalMenuItem.setAttribute("value", nsIHandlerInfo.handleInternally);
-      let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+      let label = this._prefsBundle.getFormattedString("addNewsBlogsInApp",
                                                        [this._brandShortName]);
       internalMenuItem.setAttribute("label", label);
       internalMenuItem.setAttribute("tooltiptext", label);
       internalMenuItem.setAttribute("appHandlerIcon", "feed");
       menuPopup.appendChild(internalMenuItem);
     }
 
     // Add a separator to distinguish these items from the helper app items
@@ -1780,20 +1768,18 @@ var gApplicationsPane = {
 
   _getIconURLForHandlerApp: function(aHandlerApp) {
     if (aHandlerApp instanceof nsILocalHandlerApp)
       return this._getIconURLForFile(aHandlerApp.executable);
 
     if (aHandlerApp instanceof nsIWebHandlerApp)
       return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
 
-    /* XXX-SeaMonkey: remove comment once we implement nsIWebContentHandlerInfo
     if (aHandlerApp instanceof nsIWebContentHandlerInfo)
       return this._getIconURLForWebApp(aHandlerApp.uri)
-    */
 
     // We know nothing about other kinds of handler apps.
     return "";
   },
 
   _getIconURLForFile: function(aFile) {
     var fph = ioSvc.getProtocolHandler("file")
                    .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
--- a/suite/common/utilityOverlay.js
+++ b/suite/common/utilityOverlay.js
@@ -1240,16 +1240,36 @@ function openUILinkArrayIn(urlArray, whe
   }
   for (var i = 1; i < urlArray.length; i++)
     browser.addTab(urlArray[i], null, null, false, flags);
 
   w.content.focus();
   return w;
 }
 
-//XXXCallek only used until we implement Feed Preview
-// uri is a string!
-function subscribeToFeed(uri)
-{
-  Components.classes["@mozilla.org/newsblog-feed-downloader;1"]
-            .getService(Components.interfaces.nsINewsBlogFeedDownloader)
-            .subscribeToFeed(uri, null, null);
+function subscribeToFeed(href, event) {
+  // Just load the feed in the content area to either subscribe or show the
+  // preview UI
+  var w = getTopWin();
+  var charset;
+  if (w) {
+    var browser = w.getBrowser();
+    charset = browser.characterSet;
+  }
+  else
+    // When calling this function without any open navigator window
+    charset = document.characterSet;
+  var feedURI = makeURI(href, charset);
+
+  // Use the feed scheme so X-Moz-Is-Feed will be set
+  // The value doesn't matter
+  if (/^https?/.test(feedURI.scheme))
+    href = "feed:" + href;
+  openUILink(href, event, false, true);
 }
+
+function subscribeToFeedMiddleClick(href, event) {
+  if (event.button == 1) {
+    this.subscribeToFeed(href, event);
+    // unlike for command events, we have to close the menus manually
+    closeMenus(event.target);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/suite/feeds/public/Makefile.in
@@ -0,0 +1,54 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Netscape Communications Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2001
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# 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 *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE		= suitefeeds
+XPIDL_MODULE	= suitefeeds
+
+XPIDLSRCS = \
+	nsIFeedResultService.idl \
+	nsIFeedWriter.idl \
+	nsIWebContentConverterRegistrar.idl \
+	$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/suite/feeds/public/nsIFeedResultService.idl
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Result Service.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Robert Sayre <sayrer@gmail.com>
+ *   Will Guaraldi <will.guaraldi@pculture.org>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+#include "nsISupports.idl"
+interface nsIURI;
+interface nsIRequest;
+interface nsIFeedResult;
+
+/**
+ * nsIFeedResultService provides a globally-accessible object for retrieving
+ * the results of feed processing.
+ */
+[scriptable, uuid(950a829e-c20e-4dc3-b447-f8b753ae54da)]
+interface nsIFeedResultService : nsISupports
+{
+  /**
+   * When set to true, forces the preview page to be displayed, regardless
+   * of the user's preferences.
+   */
+  attribute boolean forcePreviewPage;
+
+  /**
+   * Adds a URI to the user's specified external feed handler, or live
+   * bookmarks.
+   * @param   uri
+   *          The uri of the feed to add.
+   * @param   title
+   *          The title of the feed to add.
+   * @param   subtitle
+   *          The subtitle of the feed to add.
+   * @param   feedType
+   *          The nsIFeed type of the feed.  See nsIFeed.idl
+   */
+  void addToClientReader(in AUTF8String uri,
+                         in AString title,
+                         in AString subtitle,
+                         in unsigned long feedType);
+
+  /**
+   * Registers a Feed Result object with a globally accessible service
+   * so that it can be accessed by a singleton method outside the usual
+   * flow of control in document loading.
+   *
+   * @param   feedResult
+   *          An object implementing nsIFeedResult representing the feed.
+   */
+  void addFeedResult(in nsIFeedResult feedResult);
+
+  /**
+   * Gets a Feed Handler object registered using addFeedResult.
+   *
+   * @param   uri
+   *          The URI of the feed a handler is being requested for
+   */
+  nsIFeedResult getFeedResult(in nsIURI uri);
+
+  /**
+   * Unregisters a Feed Handler object registered using addFeedResult.
+   * @param   uri
+   *          The feed URI the handler was registered under. This must be
+   *          the same *instance* the feed was registered under.
+   */
+  void removeFeedResult(in nsIURI uri);
+};
new file mode 100644
--- /dev/null
+++ b/suite/feeds/public/nsIFeedWriter.idl
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Writer.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Asaf Romano <mano@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+
+/**
+ * Instances of this component write UI into the display page. This component
+ * is trusted so can access preferences etc, but page content isn't and so
+ * cannot.
+ */
+[scriptable, uuid(67003393-018c-4e96-af10-c6c51a049fad)]
+interface nsIFeedWriter : nsISupports
+{
+  /**
+   * Initializes the feed writer and loads the feed subscription UI.
+   * @param  aWindow
+   *         The DOMWindow of the preview page.
+   *         window.location.href == the URI of the feed.
+   */
+  void init(in nsIDOMWindow aWindow);
+
+  /**
+   * Writes the feed content, assumes that the feed writer is initialized.
+   */
+  void writeContent();
+
+  /**
+   * Uninitialize the feed writer.
+   */
+  void close();
+};
new file mode 100644
--- /dev/null
+++ b/suite/feeds/public/nsIWebContentConverterRegistrar.idl
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Web Content Converter Registrar.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+#include "nsIMIMEInfo.idl"
+#include "nsIWebContentHandlerRegistrar.idl"
+
+interface nsIRequest;
+
+[scriptable, uuid(eb361098-5158-4b21-8f98-50b445f1f0b2)]
+interface nsIWebContentHandlerInfo : nsIHandlerApp
+{
+  /**
+   * The content type handled by the handler
+   */
+  readonly attribute AString contentType;
+
+  /**
+   * The uri of the handler, with an embedded %s where the URI of the loaded
+   * document will be encoded.
+   */
+  readonly attribute AString uri;
+
+  /**
+   * Gets the service URL Spec, with the loading document URI encoded in it.
+   * @param   uri
+   *          The URI of the document being loaded
+   * @returns The URI of the service with the loading document URI encoded in
+   *          it.
+   */
+  AString getHandlerURI(in AString uri);
+};
+
+[scriptable, uuid(de7cc06e-e778-45cb-b7db-7a114e1e75b1)]
+interface nsIWebContentConverterService : nsIWebContentHandlerRegistrar
+{
+  /**
+   * Specifies the handler to be used to automatically handle all links of a
+   * certain content type from now on.
+   * @param   contentType
+   *          The content type to automatically load with the specified handler
+   * @param   handler
+   *          A web service handler. If this is null, no automatic action is
+   *          performed and the user must choose.
+   * @throws  NS_ERROR_NOT_AVAILABLE if the service refered to by |handler| is
+   *          not already registered.
+   */
+  void setAutoHandler(in AString contentType, in nsIWebContentHandlerInfo handler);
+
+  /**
+   * Gets the auto handler specified for a particular content type
+   * @param   contentType
+   *          The content type to look up an auto handler for.
+   * @returns The web service handler that will automatically handle all
+   *          documents of the specified type. null if there is no automatic
+   *          handler. (Handlers may be registered, just none of them specified
+   *          as "automatic").
+   */
+  nsIWebContentHandlerInfo getAutoHandler(in AString contentType);
+
+  /**
+   * Gets a web handler for the specified service URI
+   * @param   contentType
+   *          The content type of the service being located
+   * @param   uri
+   *          The service URI of the handler to locate.
+   * @returns A web service handler that uses the specified uri.
+   */
+  nsIWebContentHandlerInfo getWebContentHandlerByURI(in AString contentType,
+                                                     in AString uri);
+
+  /**
+   * Loads the preferred handler when content of a registered type is about
+   * to be loaded.
+   * @param   request
+   *          The nsIRequest for the load of the content
+   */
+  void loadPreferredHandler(in nsIRequest request);
+
+  /**
+   * Removes a registered protocol handler
+   * @param   protocol
+   *          The protocol scheme to remove a service handler for
+   * @param   uri
+   *          The uri of the service handler to remove
+   */
+  void removeProtocolHandler(in AString protocol, in AString uri);
+
+  /**
+   * Removes a registered content handler
+   * @param   contentType
+   *          The content type to remove a service handler for
+   * @param   uri
+   *          The uri of the service handler to remove
+   */
+  void removeContentHandler(in AString contentType, in AString uri);
+
+  /**
+   * Gets the list of content handlers for a particular type.
+   * @param   contentType
+   *          The content type to get handlers for
+   * @returns An array of nsIWebContentHandlerInfo objects
+   */
+  void getContentHandlers(in AString contentType, out unsigned long count,
+                          [retval,array,size_is(count)] out nsIWebContentHandlerInfo handlers);
+
+  /**
+   * Resets the list of available content handlers to the default set from
+   * the distribution.
+   * @param   contentType
+   *          The content type to reset handlers for
+   */
+  void resetHandlersForType(in AString contentType);
+};
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/FeedConverter.js
@@ -0,0 +1,640 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Stream Converter.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Jeff Walden <jwalden+code@mit.edu>
+ *   Will Guaraldi <will.guaraldi@pculture.org>
+ *   Caio Tiago Oliveira <asrail@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/debug.js");
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_ANY = "*/*";
+
+const FEEDHANDLER_URI = "about:feeds";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+function getPrefAppForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_APP;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_APP;
+
+    default:
+      return PREF_SELECTED_APP;
+  }
+}
+
+function getPrefWebForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_WEB;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_WEB;
+
+    default:
+      return PREF_SELECTED_WEB;
+  }
+}
+
+function getPrefActionForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_ACTION;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_ACTION;
+
+    default:
+      return PREF_SELECTED_ACTION;
+  }
+}
+
+function getPrefReaderForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_READER;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_READER;
+
+    default:
+      return PREF_SELECTED_READER;
+  }
+}
+
+function LOG(str) {
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+
+  try {
+    if (prefs.getBoolPref("feeds.log"))
+      dump("*** Feeds: " + str + "\n");
+  }
+  catch (ex) {
+  }
+}
+
+function safeGetCharPref(pref, defaultValue) {
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+  try {
+    return prefs.getCharPref(pref);
+  }
+  catch (e) {
+  }
+  return defaultValue;
+}
+
+function FeedConverter() {
+  this._ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
+                          .getService(Components.interfaces.nsIIOService);
+}
+
+FeedConverter.prototype = {
+  /**
+   * This is the downloaded text data for the feed.
+   */
+  _data: null,
+
+  /**
+   * This is the object listening to the conversion, which is ultimately the
+   * docshell for the load.
+   */
+  _listener: null,
+
+  /**
+   * Records if the feed was sniffed
+   */
+  _sniffed: false,
+
+  /**
+   * See nsISupports.idl
+   */
+  QueryInterface: XPCOMUtils.generateQI(
+    [Components.interfaces.nsIFeedResultListener,
+     Components.interfaces.nsIStreamConverter,
+     Components.interfaces.nsIStreamListener,
+     Components.interfaces.nsIRequestObserver,
+     Components.interfaces.nsISupports]),
+  classDescription: "Feed Stream Converter",
+  classID: Components.ID("{88592F45-3866-4c8e-9D8A-AB58B290FCF7}"),
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
+
+  /**
+   * See nsIStreamConverter.idl
+   */
+  convert: function convert(sourceStream, sourceType, destinationType,
+                               context) {
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  /**
+   * See nsIStreamConverter.idl
+   */
+  asyncConvertData: function asyncConvertData(sourceType, destinationType,
+                                               listener, context) {
+    this._listener = listener;
+  },
+
+  /**
+   * Whether or not the preview page is being forced.
+   */
+  _forcePreviewPage: false,
+
+  /**
+   * Release our references to various things once we're done using them.
+   */
+  _releaseHandles: function _releaseHandles() {
+    this._listener = null;
+    this._request = null;
+    this._processor = null;
+  },
+
+  /**
+   * See nsIFeedResultListener.idl
+   */
+  handleResult: function handleResult(result) {
+    // Feeds come in various content types, which our feed sniffer coerces to
+    // the maybe.feed type. However, feeds are used as a transport for
+    // different data types, e.g. news/blogs (traditional feed), video/audio
+    // (podcasts) and photos (photocasts, photostreams). Each of these is
+    // different in that there's a different class of application suitable for
+    // handling feeds of that type, but without a content-type differentiation
+    // it is difficult for us to disambiguate.
+    //
+    // The other problem is that if the user specifies an auto-action handler
+    // for one feed application, the fact that the content type is shared means
+    // that all other applications will auto-load with that handler too,
+    // regardless of the content-type.
+    //
+    // This means that content-type alone is not enough to determine whether
+    // or not a feed should be auto-handled. Therefore for feeds we need
+    // to always use this stream converter, even when an auto-action is
+    // specified, not the basic one provided by WebContentConverter. This
+    // converter needs to consume all of the data and parse it, and based on
+    // that determination make a judgement about type.
+    //
+    // Since there are no content types for this content, and I'm not going to
+    // invent any, the upshot is that while a user can set an auto-handler for
+    // generic feed content, the system will prevent them from setting an auto-
+    // handler for other stream types. In those cases, the user will always see
+    // the preview page and have to select a handler. We can guess and show
+    // a client handler, but will not be able to show web handlers for those
+    // types.
+    //
+    // If this is just a feed, not some kind of specialized application, then
+    // auto-handlers can be set and we should obey them.
+    try {
+      var feedService = Components.classes["@mozilla.org/browser/feeds/result-service;1"]
+                                  .getService(Components.interfaces.nsIFeedResultService);
+      if (!this._forcePreviewPage && result.doc) {
+        var feed = result.doc.QueryInterface(Components.interfaces.nsIFeed);
+        var handler = safeGetCharPref(getPrefActionForType(feed.type), "ask");
+
+        if (handler != "ask") {
+          if (handler == "reader")
+            handler = safeGetCharPref(getPrefReaderForType(feed.type), "messenger");
+          switch (handler) {
+            case "web":
+              var wccr = Components.classes["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]
+                                   .getService(Components.interfaces.nsIWebContentConverterService);
+              if ((feed.type == Components.interfaces.nsIFeed.TYPE_FEED &&
+                   wccr.getAutoHandler(TYPE_MAYBE_FEED)) ||
+                  (feed.type == Components.interfaces.nsIFeed.TYPE_VIDEO &&
+                   wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) ||
+                  (feed.type == Components.interfaces.nsIFeed.TYPE_AUDIO &&
+                   wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) {
+                wccr.loadPreferredHandler(this._request);
+                return;
+              }
+              break;
+
+            default:
+              LOG("unexpected handler: " + handler);
+              // fall through -- let feed service handle error
+            case "bookmarks":
+            case "client":
+            case "messenger":
+              try {
+                var title = feed.title ? feed.title.plainText() : "";
+                var desc = feed.subtitle ? feed.subtitle.plainText() : "";
+                feedService.addToClientReader(result.uri.spec, title, desc, feed.type);
+                return;
+              } catch(ex) {
+                /* fallback to preview mode */
+              }
+          }
+        }
+      }
+
+      var chromeChannel;
+
+      // show the feed page if it wasn't sniffed and we have a document,
+      // or we have a document, title, and link or id
+      if (result.doc && (!this._sniffed ||
+          (result.doc.title && (result.doc.link || result.doc.id)))) {
+
+        // If there was no automatic handler, or this was a podcast,
+        // photostream or some other kind of application, we must always
+        // show the preview page.
+
+        // Store the result in the result service so that the display
+        // page can access it.
+
+        feedService.addFeedResult(result);
+
+        // Now load the actual XUL document.
+        var chromeURI = this._ioSvc.newURI(FEEDHANDLER_URI, null, null);
+        chromeChannel = this._ioSvc.newChannelFromURI(chromeURI, null);
+        chromeChannel.originalURI = result.uri;
+      }
+      else
+        chromeChannel = this._ioSvc.newChannelFromURI(result.uri, null);
+
+      chromeChannel.loadGroup = this._request.loadGroup;
+      chromeChannel.asyncOpen(this._listener, null);
+    }
+    finally {
+      this._releaseHandles();
+    }
+  },
+
+  /**
+   * See nsIStreamListener.idl
+   */
+  onDataAvailable: function onDataAvailable(request, context, inputStream,
+                                             sourceOffset, count) {
+    if (this._processor)
+      this._processor.onDataAvailable(request, context, inputStream,
+                                      sourceOffset, count);
+  },
+
+  /**
+   * See nsIRequestObserver.idl
+   */
+  onStartRequest: function onStartRequest(request, context) {
+    var channel = request.QueryInterface(Components.interfaces.nsIChannel);
+
+    // Check for a header that tells us there was no sniffing
+    // The value doesn't matter.
+    try {
+      var httpChannel = channel.QueryInterface(Components.interfaces.nsIHttpChannel);
+      var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed");
+    }
+    catch (ex) {
+      this._sniffed = true;
+    }
+
+    this._request = request;
+
+    // Save and reset the forced state bit early, in case there's some kind of
+    // error.
+    var feedService = Components.classes["@mozilla.org/browser/feeds/result-service;1"]
+                                .getService(Components.interfaces.nsIFeedResultService);
+    this._forcePreviewPage = feedService.forcePreviewPage;
+    feedService.forcePreviewPage = false;
+
+    // Parse feed data as it comes in
+    this._processor = Components.classes["@mozilla.org/feed-processor;1"]
+                                .createInstance(Components.interfaces.nsIFeedProcessor);
+    this._processor.listener = this;
+    this._processor.parseAsync(null, channel.URI);
+
+    this._processor.onStartRequest(request, context);
+  },
+
+  /**
+   * See nsIRequestObserver.idl
+   */
+  onStopRequest: function onStopRequest(request, context, status) {
+    if (this._processor)
+      this._processor.onStopRequest(request, context, status);
+  }
+
+};
+
+/**
+ * Helper to register multiple components sharing the same prototype
+ * using XPCOMUtils.
+ */
+function build_component(component, ctor, properties) {
+  component.prototype = new ctor();
+  for (let name in properties) {
+    component.prototype[name] = properties[name];
+  }
+}
+
+function FeedConverter_feed() {
+}
+
+build_component(FeedConverter_feed, FeedConverter,
+                {contractID: "@mozilla.org/streamconv;1?from="
+                             + TYPE_MAYBE_FEED + "&to="
+                             + TYPE_ANY});
+
+function FeedConverter_audio_feed() {
+}
+
+build_component(FeedConverter_audio_feed, FeedConverter,
+                {contractID: "@mozilla.org/streamconv;1?from="
+                             + TYPE_MAYBE_AUDIO_FEED + "&to="
+                             + TYPE_ANY});
+
+function FeedConverter_video_feed() {
+}
+
+build_component(FeedConverter_video_feed, FeedConverter,
+                {contractID: "@mozilla.org/streamconv;1?from="
+                             + TYPE_MAYBE_VIDEO_FEED + "&to="
+                             + TYPE_ANY});
+
+/**
+ * Keeps parsed FeedResults around for use elsewhere in the UI after the stream
+ * converter completes.
+ */
+function FeedResultService() {
+  this._ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
+                          .getService(Components.interfaces.nsIIOService);
+}
+
+FeedResultService.prototype = {
+  /**
+   * A URI spec -> [nsIFeedResult] hash. We have to keep a list as the
+   * value in case the same URI is requested concurrently.
+   */
+  _results: { },
+
+  /**
+   * See nsIFeedResultService.idl
+   */
+  forcePreviewPage: false,
+
+  /**
+   * See nsIFeedResultService.idl
+   */
+  addToClientReader: function addToClientReader(spec, title, subtitle, feedType) {
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch);
+
+    var handler = safeGetCharPref(getPrefActionForType(feedType), "reader");
+    if (handler == "ask" || handler == "reader")
+      handler = safeGetCharPref(getPrefReaderForType(feedType), "messenger");
+
+    switch (handler) {
+    case "client":
+      var clientApp = prefs.getComplexValue(getPrefAppForType(feedType),
+                                            Components.interfaces.nsILocalFile);
+
+      // For the benefit of applications that might know how to deal with more
+      // URLs than just feeds, send feed: URLs in the following format:
+      //
+      // http urls: replace scheme with feed, e.g.
+      // http://foo.com/index.rdf -> feed://foo.com/index.rdf
+      // other urls: prepend feed: scheme, e.g.
+      // https://foo.com/index.rdf -> feed:https://foo.com/index.rdf
+      var feedURI = this._ioSvc.newURI(spec, null, null);
+      if (feedURI.schemeIs("http")) {
+        feedURI.scheme = "feed";
+        spec = feedURI.spec;
+      }
+      else
+        spec = "feed:" + spec;
+
+      // Retrieving the shell service might fail on some systems, most
+      // notably systems where GNOME is not installed.
+      try {
+        var ss = Components.classes["@mozilla.org/suite/shell-service;1"]
+                           .getService(Components.interfaces.nsIShellService);
+        ss.openApplicationWithURI(clientApp, spec);
+      } catch(e) {
+        // If we couldn't use the shell service, fallback to using a
+        // nsIProcess instance
+        var p = Components.classes["@mozilla.org/process/util;1"]
+                          .createInstance(Components.interfaces.nsIProcess);
+        p.init(clientApp);
+        p.run(false, [spec], 1);
+      }
+      break;
+
+    default:
+      // "web" should have been handled elsewhere
+      LOG("unexpected handler: " + handler);
+      // fall through
+    case "bookmarks":
+    case "messenger":
+      Components.classes["@mozilla.org/newsblog-feed-downloader;1"]
+                .getService(Components.interfaces.nsINewsBlogFeedDownloader)
+                .subscribeToFeed(spec, null, null);
+      break;
+
+    }
+  },
+
+  /**
+   * See nsIFeedResultService.idl
+   */
+  addFeedResult: function addFeedResult(feedResult) {
+    NS_ASSERT(feedResult != null, "null feedResult!");
+    NS_ASSERT(feedResult.uri != null, "null URI!");
+    var spec = feedResult.uri.spec;
+    if (!this._results[spec])
+      this._results[spec] = [];
+    this._results[spec].push(feedResult);
+  },
+
+  /**
+   * See nsIFeedResultService.idl
+   */
+  getFeedResult: function getFeedResult(uri) {
+    NS_ASSERT(uri != null, "null URI!");
+    var resultList = this._results[uri.spec];
+    for (let i = 0; i < resultList.length; ++i) {
+      if (resultList[i].uri == uri)
+        return resultList[i];
+    }
+    return null;
+  },
+
+  /**
+   * See nsIFeedResultService.idl
+   */
+  removeFeedResult: function removeFeedResult(uri) {
+    NS_ASSERT(uri != null, "null URI!");
+    var resultList = this._results[uri.spec];
+    if (!resultList)
+      return;
+    var deletions = 0;
+    for (let i = 0; i < resultList.length; ++i) {
+      if (resultList[i].uri == uri) {
+        delete resultList[i];
+        ++deletions;
+      }
+    }
+
+    // send the holes to the end
+    resultList.sort();
+    // and trim the list
+    resultList.splice(resultList.length - deletions, deletions);
+    if (resultList.length == 0)
+      delete this._results[uri.spec];
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIFeedResultService,
+                                         Components.interfaces.nsISupports]),
+  classID: Components.ID("{E5B05E9D-F037-48e4-B9A4-B99476582927}"),
+  classDescription: "Feed Result Service",
+  contractID: "@mozilla.org/browser/feeds/result-service;1",
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT
+};
+
+/**
+ * A protocol handler that attempts to deal with the variant forms of feed:
+ * URIs that are actually either http or https.
+ */
+function _FeedProtocolHandler() {
+  this._ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
+                          .getService(Components.interfaces.nsIIOService);
+  this._http = this._ioSvc.getProtocolHandler("http");
+}
+
+_FeedProtocolHandler.prototype = {
+  get protocolFlags() {
+    return this._http.protocolFlags;
+  },
+
+  get defaultPort() {
+    return this._http.defaultPort;
+  },
+
+  allowPort: function allowPort(port, scheme) {
+    return this._http.allowPort(port, scheme);
+  },
+
+  newURI: function newURI(spec, originalCharset, baseURI) {
+    // See bug 408599 - feed URIs can be either standard URLs of the form
+    // feed://example.com, in which case the real protocol is http, or nested
+    // URIs of the form feed:realscheme:. When realscheme is either http or
+    // https, we deal with the way that creates a standard URL with the
+    // realscheme as the host by unmangling in newChannel; for others, we fail
+    // rather than let it wind up loading something like www.realscheme.com//foo
+
+    const feedSlashes = "feed://";
+    const feedHttpSlashes = "feed:http://";
+    const feedHttpsSlashes = "feed:https://";
+
+    if (spec.substr(0, feedSlashes.length) != feedSlashes &&
+        spec.substr(0, feedHttpSlashes.length) != feedHttpSlashes &&
+        spec.substr(0, feedHttpsSlashes.length) != feedHttpsSlashes)
+      throw Components.results.NS_ERROR_MALFORMED_URI;
+
+    var uri = Components.classes["@mozilla.org/network/standard-url;1"]
+                        .createInstance(Components.interfaces.nsIStandardURL);
+    uri.init(Components.interfaces.nsIStandardURL.URLTYPE_STANDARD, 80, spec,
+             originalCharset, baseURI);
+    return uri;
+  },
+
+  newChannel: function newChannel(aUri) {
+    // feed: URIs either start feed://, in which case the real scheme is http:
+    // or feed:http(s)://, (which by now we've changed to feed://realscheme//)
+    var feedSpec = aUri.spec;
+    const httpChunk = /^feed:\/\/(https?)/;
+    if (httpChunk.test(feedSpec))
+      feedSpec = feedSpec.replace(httpChunk, "$1:");
+    else
+      feedSpec = feedSpec.replace(/^feed/, "http");
+
+    var uri = this._ioSvc.newURI(feedSpec, aUri.originCharset, null);
+    var channel = this._ioSvc.newChannelFromURI(uri, null)
+                       .QueryInterface(Components.interfaces.nsIHttpChannel);
+    // Set this so we know this is supposed to be a feed
+    channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
+    channel.originalURI = aUri;
+    return channel;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIProtocolHandler,
+                                         Components.interfaces.nsISupports]),
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT
+};
+
+function FeedProtocolHandler() {
+}
+
+build_component(FeedProtocolHandler, _FeedProtocolHandler,
+                {classID: Components.ID("{A95D7F48-11BE-4324-8872-D23BD79FE78B}"),
+                 classDescription: "Feed Protocol Handler",
+                 contractID: "@mozilla.org/network/protocol;1?name=feed",
+                 scheme: "feed"
+                });
+
+function PodcastProtocolHandler() {
+}
+
+build_component(PodcastProtocolHandler, _FeedProtocolHandler,
+                {classID: Components.ID("{F0FF0FE4-1713-4d34-9323-3F5DEB6A6A60}"),
+                 classDescription: "Podcast Protocol Handler",
+                 contractID: "@mozilla.org/network/protocol;1?name=pcast",
+                 scheme: "pcast"
+                });
+
+var components = [FeedProtocolHandler, PodcastProtocolHandler, FeedResultService,
+                  FeedConverter_feed, FeedConverter_audio_feed,
+                  FeedConverter_video_feed];
+
+function NSGetModule(cm, file) {
+  return XPCOMUtils.generateModule(components);
+}
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/FeedWriter.js
@@ -0,0 +1,1442 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Writer.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Jeff Walden <jwalden+code@mit.edu>
+ *   Asaf Romano <mano@mozilla.com>
+ *   Robert Sayre <sayrer@gmail.com>
+ *   Michael Ventnor <m.ventnor@gmail.com>
+ *   Will Guaraldi <will.guaraldi@pculture.org>
+ *   Caio Tiago Oliveira <asrail@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const XML_NS = "http://www.w3.org/XML/1998/namespace";
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const STRING_BUNDLE_URI = "chrome://communicator/locale/feeds/subscribe.properties";
+const SUBSCRIBE_PAGE_URI = "chrome://communicator/content/feeds/subscribe.xhtml";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
+
+const TITLE_ID = "feedTitleText";
+const SUBTITLE_ID = "feedSubtitleText";
+
+function getPrefAppForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_APP;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_APP;
+
+    default:
+      return PREF_SELECTED_APP;
+  }
+}
+
+function getPrefWebForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_WEB;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_WEB;
+
+    default:
+      return PREF_SELECTED_WEB;
+  }
+}
+
+function getPrefActionForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_ACTION;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_ACTION;
+
+    default:
+      return PREF_SELECTED_ACTION;
+  }
+}
+
+function getPrefReaderForType(t) {
+  switch (t) {
+    case Components.interfaces.nsIFeed.TYPE_VIDEO:
+      return PREF_VIDEO_SELECTED_READER;
+
+    case Components.interfaces.nsIFeed.TYPE_AUDIO:
+      return PREF_AUDIO_SELECTED_READER;
+
+    default:
+      return PREF_SELECTED_READER;
+  }
+}
+
+function LOG(str) {
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+
+  try {
+    if (prefs.getBoolPref("feeds.log"))
+      dump("*** Feeds: " + str + "\n");
+  }
+  catch (ex) {
+  }
+}
+
+function safeGetCharPref(pref, defaultValue) {
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+  try {
+    return prefs.getCharPref(pref);
+  }
+  catch (e) {
+  }
+  return defaultValue;
+}
+
+/**
+ * Wrapper function for nsIIOService::newURI.
+ * @param aURLSpec
+ *        The URL string from which to create an nsIURI.
+ * @returns an nsIURI object, or null if the creation of the URI failed.
+ */
+function makeURI(aURLSpec, aCharset) {
+  try {
+    var ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
+                          .getService(Components.interfaces.nsIIOService);
+    return ioSvc.newURI(aURLSpec, aCharset, null);
+  } catch (ex) {
+  }
+
+  return null;
+}
+
+/**
+ * Converts a number of bytes to the appropriate unit that results in a
+ * number that needs fewer than 4 digits
+ *
+ * @return a pair: [new value with 3 sig. figs., its unit]
+  */
+function convertByteUnits(aBytes) {
+  var units = ["bytes", "kilobytes", "megabytes", "gigabytes"];
+  var unitIndex = 0;
+
+  // convert to next unit if it needs 4 digits (after rounding), but only if
+  // we know the name of the next unit
+  while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
+    aBytes /= 1024;
+    unitIndex++;
+  }
+
+  // Get rid of insignificant bits by truncating to 1 or 0 decimal points
+  // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
+  aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
+
+  return [aBytes, units[unitIndex]];
+}
+
+function FeedWriter() {
+  this._ioSvc = Components.classes["@mozilla.org/network/io-service;1"]
+                          .getService(Components.interfaces.nsIIOService);
+  this._mimeSvc = Components.classes["@mozilla.org/mime;1"]
+                            .getService(Components.interfaces.nsIMIMEService);
+}
+
+FeedWriter.prototype = {
+  _getPropertyAsBag: function getPropertyAsBag(container, property) {
+    return container.fields.getProperty(property)
+                    .QueryInterface(Components.interfaces.nsIPropertyBag2);
+  },
+
+  _getPropertyAsString: function getPropertyAsString(container, property) {
+    try {
+      return container.fields.getPropertyAsAString(property);
+    }
+    catch (e) {
+    }
+    return "";
+  },
+
+  _setContentText: function setContentText(id, text) {
+    this._contentSandbox.element = this._document.getElementById(id);
+    this._contentSandbox.textNode = this._document.createTextNode(text);
+    var codeStr = "while (element.hasChildNodes()) " +
+                  "  element.removeChild(element.firstChild);" +
+                  "element.appendChild(textNode);";
+    Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+    this._contentSandbox.element = null;
+    this._contentSandbox.textNode = null;
+  },
+
+  /**
+   * Safely sets the href attribute on an anchor tag, providing the URI
+   * specified can be loaded according to rules.
+   * @param   element
+   *          The element to set a URI attribute on
+   * @param   attribute
+   *          The attribute of the element to set the URI to, e.g. href or src
+   * @param   uri
+   *          The URI spec to set as the href
+   */
+  _safeSetURIAttribute: function safeSetURIAttribute(element, attribute, uri) {
+    var secman = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                           .getService(Components.interfaces.nsIScriptSecurityManager);
+    const flags = Components.interfaces.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
+    try {
+      secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
+      // checkLoadURIStrWithPrincipal will throw if the link URI should not be
+      // loaded, either because our feedURI isn't allowed to load it or per
+      // the rules specified in |flags|, so we'll never "linkify" the link...
+      this._contentSandbox.element = element;
+      this._contentSandbox.uri = uri;
+      var codeStr = "element.setAttribute('" + attribute + "', uri);";
+      Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+    }
+    catch (e) {
+      // Not allowed to load this link because secman.checkLoadURIStr threw
+    }
+  },
+
+  /**
+   * Use this sandbox to run any dom manipulation code on nodes which
+   * are already inserted into the content document.
+   */
+  __contentSandbox: null,
+  get _contentSandbox() {
+    if (!this.__contentSandbox)
+      this.__contentSandbox = new Components.utils.Sandbox(this._window);
+
+    return this.__contentSandbox;
+  },
+
+  /**
+   * Calls doCommand for a the given XUL element within the context of the
+   * content document.
+   *
+   * @param aElement
+   *        the XUL element to call doCommand() on.
+   */
+  _safeDoCommand: function safeDoCommand(aElement) {
+    this._contentSandbox.element = aElement;
+    Components.utils.evalInSandbox("element.doCommand();", this._contentSandbox);
+    this._contentSandbox.element = null;
+  },
+
+  __faviconService: null,
+  get _faviconService() {
+    if (!this.__faviconService)
+      this.__faviconService = Components.classes["@mozilla.org/browser/favicon-service;1"]
+                                        .getService(Components.interfaces.nsIFaviconService);
+
+    return this.__faviconService;
+  },
+
+  __bundle: null,
+  get _bundle() {
+    if (!this.__bundle) {
+      this.__bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                                .getService(Components.interfaces.nsIStringBundleService)
+                                .createBundle(STRING_BUNDLE_URI);
+    }
+
+    return this.__bundle;
+  },
+
+  _getFormattedString: function getFormattedString(key, params) {
+    return this._bundle.formatStringFromName(key, params, params.length);
+  },
+
+  _getString: function getString(key) {
+    try {
+      return this._bundle.GetStringFromName(key);
+    } catch(e) {
+      LOG("Couldn't retrieve key from bundle");
+    }
+
+    return null;
+  },
+
+  /* Magic helper methods to be used instead of xbl properties */
+  _getSelectedItemFromMenulist: function getSelectedItemFromList(aList) {
+    return aList.getElementsByAttribute("selected", "true").item(0);
+  },
+
+  _setCheckboxCheckedState: function setCheckboxCheckedState(aCheckbox, aValue) {
+    // see checkbox.xml, xbl bindings are not applied within the sandbox!
+    this._contentSandbox.checkbox = aCheckbox;
+    var codeStr;
+    var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
+    if (aValue)
+      codeStr = "checkbox.setAttribute('checked', 'true'); ";
+    else
+      codeStr = "checkbox.removeAttribute('checked'); ";
+
+    if (change) {
+      this._contentSandbox.document = this._document;
+      codeStr += "var event = document.createEvent('Events'); " +
+                 "event.initEvent('CheckboxStateChange', true, true);" +
+                 "checkbox.dispatchEvent(event);";
+    }
+
+    Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+  },
+
+   /**
+   * Returns a date suitable for displaying in the feed preview.
+   * If the date cannot be parsed, the return value is "null".
+   * @param   dateString
+   *          A date as extracted from a feed entry. (entry.updated)
+   */
+  _parseDate: function parseDate(dateString) {
+    // Convert the date into the user's local time zone
+    var dateObj = new Date(dateString);
+
+    // Make sure the date we're given is valid.
+    if (isNaN(dateObj.getTime()))
+      return null;
+
+    var dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+                                .getService(Components.interfaces.nsIScriptableDateFormat);
+    return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
+                                      dateObj.getFullYear(), dateObj.getMonth()+1, dateObj.getDate(),
+                                      dateObj.getHours(), dateObj.getMinutes(), dateObj.getSeconds());
+  },
+
+  /**
+   * Returns the feed type.
+   */
+  __feedType: null,
+  _getFeedType: function getFeedType() {
+    if (this.__feedType != null)
+      return this.__feedType;
+
+    try {
+      // grab the feed because it's got the feed.type in it.
+      var container = this._getContainer();
+      var feed = container.QueryInterface(Components.interfaces.nsIFeed);
+      this.__feedType = feed.type;
+      return feed.type;
+    } catch (ex) {
+    }
+
+    return Components.interfaces.nsIFeed.TYPE_FEED;
+  },
+
+  /**
+   * Maps a feed type to a maybe-feed mimetype.
+   */
+  _getMimeTypeForFeedType: function getMimeTypeForFeedType() {
+    switch (this._getFeedType()) {
+      case Components.interfaces.nsIFeed.TYPE_VIDEO:
+        return TYPE_MAYBE_VIDEO_FEED;
+
+      case Components.interfaces.nsIFeed.TYPE_AUDIO:
+        return TYPE_MAYBE_AUDIO_FEED;
+
+      default:
+        return TYPE_MAYBE_FEED;
+    }
+  },
+
+  /**
+   * Writes the feed title into the preview document.
+   * @param   container
+   *          The feed container
+   */
+  _setTitleText: function setTitleText(container) {
+    if (container.title) {
+      var title = container.title.plainText();
+      this._setContentText(TITLE_ID, title);
+      this._contentSandbox.document = this._document;
+      this._contentSandbox.title = title;
+      var codeStr = "document.title = title;"
+      Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+    }
+
+    var feed = container.QueryInterface(Components.interfaces.nsIFeed);
+    if (feed && feed.subtitle)
+      this._setContentText(SUBTITLE_ID, container.subtitle.plainText());
+  },
+
+  /**
+   * Writes the title image into the preview document if one is present.
+   * @param   container
+   *          The feed container
+   */
+  _setTitleImage: function setTitleImage(container) {
+    try {
+      var parts = container.image;
+
+      // Set up the title image (supplied by the feed)
+      var feedTitleImage = this._document.getElementById("feedTitleImage");
+      this._safeSetURIAttribute(feedTitleImage, "src",
+                                parts.getPropertyAsAString("url"));
+
+      // Set up the title image link
+      var feedTitleLink = this._document.getElementById("feedTitleLink");
+
+      var titleText = this._getFormattedString("linkTitleTextFormat",
+                                               [parts.getPropertyAsAString("title")]);
+      this._contentSandbox.feedTitleLink = feedTitleLink;
+      this._contentSandbox.titleText = titleText;
+      this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
+      this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
+
+      // Fix the margin on the main title, so that the image doesn't run over
+      // the underline
+      var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
+                    "feedTitleText.style.MozMarginEnd = titleImageWidth + 'px';";
+      Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+      this._contentSandbox.feedTitleLink = null;
+      this._contentSandbox.titleText = null;
+      this._contentSandbox.feedTitleText = null;
+      this._contentSandbox.titleImageWidth = null;
+
+      this._safeSetURIAttribute(feedTitleLink, "href",
+                                parts.getPropertyAsAString("link"));
+    }
+    catch (e) {
+      LOG("Failed to set Title Image (this is benign): " + e);
+    }
+  },
+
+  /**
+   * Writes all entries contained in the feed.
+   * @param   container
+   *          The container of entries in the feed
+   */
+  _writeFeedContent: function writeFeedContent(container) {
+    // Build the actual feed content
+    var feed = container.QueryInterface(Components.interfaces.nsIFeed);
+    if (feed.items.length == 0)
+      return;
+
+    this._contentSandbox.feedContent =
+      this._document.getElementById("feedContent");
+
+    for (let i = 0; i < feed.items.length; ++i) {
+      let entry = feed.items.queryElementAt(i, Components.interfaces.nsIFeedEntry);
+      entry.QueryInterface(Components.interfaces.nsIFeedContainer);
+
+      let entryContainer = this._document.createElementNS(HTML_NS, "div");
+      entryContainer.className = "entry";
+
+      // If the entry has a title, make it a link
+      if (entry.title) {
+        let a = this._document.createElementNS(HTML_NS, "a");
+        a.appendChild(this._document.createTextNode(entry.title.plainText()));
+
+        // Entries are not required to have links, so entry.link can be null.
+        if (entry.link)
+          this._safeSetURIAttribute(a, "href", entry.link.spec);
+
+        let title = this._document.createElementNS(HTML_NS, "h3");
+        title.appendChild(a);
+
+        let lastUpdated = this._parseDate(entry.updated);
+        if (lastUpdated) {
+          let dateDiv = this._document.createElementNS(HTML_NS, "div");
+          dateDiv.className = "lastUpdated";
+          dateDiv.textContent = lastUpdated;
+          title.appendChild(dateDiv);
+        }
+
+        entryContainer.appendChild(title);
+      }
+
+      var body = this._document.createElementNS(HTML_NS, "div");
+      var summary = entry.summary || entry.content;
+      var docFragment = null;
+      if (summary) {
+        if (summary.base)
+          body.setAttributeNS(XML_NS, "base", summary.base.spec);
+        else
+          LOG("no base?");
+        docFragment = summary.createDocumentFragment(body);
+        if (docFragment)
+          body.appendChild(docFragment);
+
+        // If the entry doesn't have a title, append a # permalink
+        // See http://scripting.com/rss.xml for an example
+        if (!entry.title && entry.link) {
+          var a = this._document.createElementNS(HTML_NS, "a");
+          a.appendChild(this._document.createTextNode("#"));
+          this._safeSetURIAttribute(a, "href", entry.link.spec);
+          body.appendChild(this._document.createTextNode(" "));
+          body.appendChild(a);
+        }
+
+      }
+      body.className = "feedEntryContent";
+      entryContainer.appendChild(body);
+
+      if (entry.enclosures && entry.enclosures.length > 0) {
+        var enclosuresDiv = this._buildEnclosureDiv(entry);
+        entryContainer.appendChild(enclosuresDiv);
+      }
+
+      this._contentSandbox.entryContainer = entryContainer;
+      this._contentSandbox.clearDiv = this._document
+                                          .createElementNS(HTML_NS, "div");
+      this._contentSandbox.clearDiv.style.clear = "both";
+
+      var codeStr = "feedContent.appendChild(entryContainer); " +
+                    "feedContent.appendChild(clearDiv);";
+      Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+    }
+
+    this._contentSandbox.feedContent = null;
+    this._contentSandbox.entryContainer = null;
+    this._contentSandbox.clearDiv = null;
+  },
+
+  /**
+   * Takes a url to a media item and returns the best name it can come up with.
+   * Frequently this is the filename portion (e.g. passing in
+   * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
+   * cases, this will return the entire url (e.g. passing in
+   * http://example.com/somedirectory/ would return
+   * http://example.com/somedirectory/).
+   * @param aURL
+   *        The URL string from which to create a display name
+   * @returns a string
+   */
+  _getURLDisplayName: function getURLDisplayName(aURL) {
+    var url = makeURI(aURL);
+
+    if ((url instanceof Components.interfaces.nsIURL) && url.fileName)
+      return decodeURI(url.fileName);
+    return aURL;
+  },
+
+  /**
+   * Takes a FeedEntry with enclosures, generates the HTML code to represent
+   * them, and returns that.
+   * @param   entry
+   *          FeedEntry with enclosures
+   * @returns element
+   */
+  _buildEnclosureDiv: function buildEnclosureDiv(entry) {
+    var enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
+    enclosuresDiv.className = "enclosures";
+
+    enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
+
+    for (let i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
+      let enc = entry.enclosures.queryElementAt(i_enc, Components.interfaces.nsIWritablePropertyBag2);
+
+      if (!(enc.hasKey("url")))
+        continue;
+
+      let enclosureDiv = this._document.createElementNS(HTML_NS, "div");
+      enclosureDiv.setAttribute("class", "enclosure");
+
+      let mozicon = "moz-icon://.txt?size=16";
+      let type_text = null;
+      let size_text = null;
+
+      if (enc.hasKey("type")) {
+        type_text = enc.get("type");
+        try {
+          let handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
+
+          if (handlerInfoWrapper)
+            type_text = handlerInfoWrapper.description;
+
+          if  (type_text && type_text.length > 0)
+            mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
+
+        } catch (ex) {
+        }
+
+      }
+
+      if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
+        let enc_size = convertByteUnits(parseInt(enc.get("length")));
+
+        let size_text = this._getFormattedString("enclosureSizeText",
+                             [enc_size[0], this._getString(enc_size[1])]);
+      }
+
+      let iconimg = this._document.createElementNS(HTML_NS, "img");
+      iconimg.setAttribute("src", mozicon);
+      iconimg.setAttribute("class", "type-icon");
+      enclosureDiv.appendChild(iconimg);
+
+      enclosureDiv.appendChild(this._document.createTextNode( " " ));
+
+      let enc_href = this._document.createElementNS(HTML_NS, "a");
+      enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
+      this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
+      enclosureDiv.appendChild(enc_href);
+
+      if (type_text && size_text)
+        enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
+
+      else if (type_text)
+        enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
+
+      else if (size_text)
+        enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
+
+      enclosuresDiv.appendChild(enclosureDiv);
+    }
+
+    return enclosuresDiv;
+  },
+
+  /**
+   * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
+   * Displays error information if there was one.
+   * @param   result
+   *          The parsed feed result
+   * @returns A valid nsIFeedContainer object containing the contents of
+   *          the feed.
+   */
+  _getContainer: function getContainer(result) {
+    var feedService = Components.classes["@mozilla.org/browser/feeds/result-service;1"]
+                                .getService(Components.interfaces.nsIFeedResultService);
+
+    try {
+      var result = feedService.getFeedResult(this._getOriginalURI(this._window));
+
+      if (result.bozo) {
+        LOG("Subscribe Preview: feed result is bozo?!");
+      }
+    }
+    catch (e) {
+      LOG("Subscribe Preview: feed not available?!");
+    }
+
+    try {
+      var container = result.doc;
+    }
+    catch (e) {
+      LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
+      return null;
+    }
+    return container;
+  },
+
+  /**
+   * Get the human-readable display name of a file. This could be the
+   * application name.
+   * @param   file
+   *          A nsIFile to look up the name of
+   * @returns The display name of the application represented by the file.
+   */
+  _getFileDisplayName: function getFileDisplayName(file) {
+#ifdef XP_WIN
+    if (file instanceof Components.interfaces.nsILocalFileWin) {
+      try {
+        return file.getVersionInfoField("FileDescription");
+      }
+      catch (e) {
+      }
+    }
+#endif
+#ifdef XP_MACOSX
+    var lfm = file.QueryInterface(Components.interfaces.nsILocalFileMac);
+    try {
+      return lfm.bundleDisplayName;
+    }
+    catch (e) {
+      // fall through to the file name
+    }
+#endif
+    var url = this._ioSvc.newFileURI(file).QueryInterface(Components.interfaces.nsIURL);
+    return url.fileName;
+  },
+
+  /**
+   * Get moz-icon url for a file
+   * @param   file
+   *          A nsIFile object for which the moz-icon:// is returned
+   * @returns moz-icon url of the given file as a string
+   */
+  _getFileIconURL: function getFileIconURL(file) {
+    var fph = this._ioSvc.getProtocolHandler("file")
+                  .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+    var urlSpec = fph.getURLSpecFromFile(file);
+    return "moz-icon://" + urlSpec + "?size=16";
+  },
+
+  /**
+   * Helper method to set the selected application and system default
+   * reader menuitems details from a file object
+   *   @param aMenuItem
+   *          The menuitem on which the attributes should be set
+   *   @param aFile
+   *          The menuitem's associated file
+   */
+  _initMenuItemWithFile: function(aMenuItem, aFile) {
+    this._contentSandbox.menuitem = aMenuItem;
+    this._contentSandbox.label = this._getFileDisplayName(aFile);
+    this._contentSandbox.image = this._getFileIconURL(aFile);
+    var codeStr = "menuitem.setAttribute('label', label); " +
+                  "menuitem.setAttribute('image', image);";
+    Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+  },
+
+  /**
+   * Displays a prompt from which the user may choose a (client) feed reader.
+   * @return - true if a feed reader was selected, false otherwise.
+   */
+  _chooseClientApp: function chooseClientApp() {
+    try {
+      var fp = Components.classes["@mozilla.org/filepicker;1"]
+                         .createInstance(Components.interfaces.nsIFilePicker);
+      fp.init(this._window,
+              this._getString("chooseApplicationDialogTitle"),
+              Components.interfaces.nsIFilePicker.modeOpen);
+      fp.appendFilters(Components.interfaces.nsIFilePicker.filterApps);
+
+      if (fp.show() == Components.interfaces.nsIFilePicker.returnOK) {
+        this._selectedApp = fp.file;
+        if (this._selectedApp) {
+          // XXXben - we need to compare this with the running instance executable
+          //          just don't know how to do that via script...
+          // XXXmano TBD: can probably add this to nsIShellService
+#ifdef XP_WIN
+#expand           if (fp.file.leafName != "__MOZ_APP_NAME__.exe") {
+#else
+#ifdef XP_MACOSX
+#expand           if (fp.file.leafName != "__MOZ_APP_DISPLAYNAME__.app") {
+#else
+#expand           if (fp.file.leafName != "__MOZ_APP_NAME__-bin") {
+#endif
+#endif
+            this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
+                                       this._selectedApp);
+
+            // Show and select the selected application menuitem
+            var codeStr = "selectedAppMenuItem.hidden = false;" +
+                          "selectedAppMenuItem.doCommand();";
+            Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+            return true;
+          }
+        }
+      }
+    }
+    catch(ex) {
+    }
+
+    return false;
+  },
+
+  _setAlwaysUseCheckedState: function setAlwaysUseCheckedState(feedType) {
+    var checkbox = this._document.getElementById("alwaysUse");
+    if (checkbox) {
+      var alwaysUse = (safeGetCharPref(getPrefActionForType(feedType), "ask") != "ask");
+      this._setCheckboxCheckedState(checkbox, alwaysUse);
+    }
+  },
+
+  _setSubscribeUsingLabel: function setSubscribeUsingLabel() {
+    var stringLabel = "subscribeFeedUsing";
+    switch (this._getFeedType()) {
+      case Components.interfaces.nsIFeed.TYPE_VIDEO:
+        stringLabel = "subscribeVideoPodcastUsing";
+        break;
+
+      case Components.interfaces.nsIFeed.TYPE_AUDIO:
+        stringLabel = "subscribeAudioPodcastUsing";
+        break;
+    }
+
+    this._contentSandbox.subscribeUsing =
+      this._document.getElementById("subscribeUsingDescription");
+    this._contentSandbox.label = this._getString(stringLabel);
+    var codeStr = "subscribeUsing.setAttribute('value', label);"
+    Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+  },
+
+  _setAlwaysUseLabel: function setAlwaysUseLabel() {
+    var checkbox = this._document.getElementById("alwaysUse");
+    if (checkbox) {
+      var handlersMenuList = this._document.getElementById("handlersMenuList");
+      if (handlersMenuList) {
+        var handlerName = this._getSelectedItemFromMenulist(handlersMenuList)
+                              .getAttribute("label");
+        var stringLabel = "alwaysUseForFeeds";
+        switch (this._getFeedType()) {
+          case Components.interfaces.nsIFeed.TYPE_VIDEO:
+            stringLabel = "alwaysUseForVideoPodcasts";
+            break;
+
+          case Components.interfaces.nsIFeed.TYPE_AUDIO:
+            stringLabel = "alwaysUseForAudioPodcasts";
+            break;
+        }
+
+        this._contentSandbox.checkbox = checkbox;
+        this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
+
+        var codeStr = "checkbox.setAttribute('label', label);";
+        Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+      }
+    }
+  },
+
+  // nsIDomEventListener
+  handleEvent: function(event) {
+    // see comments in init()
+    event = new XPCNativeWrapper(event);
+    if (event.target.ownerDocument != this._document) {
+      LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
+      return;
+    }
+
+    if (event.type == "command") {
+      switch (event.target.id) {
+        case "subscribeButton":
+          this.subscribe();
+          break;
+        case "chooseApplicationMenuItem":
+          /* Bug 351263: Make sure to not steal focus if the "Choose
+           * Application" item is being selected with the keyboard. We do this
+           * by ignoring command events while the dropdown is closed (user
+           * arrowing through the combobox), but handling them while the
+           * combobox dropdown is open (user pressed enter when an item was
+           * selected). If we don't show the filepicker here, it will be shown
+           * when clicking "Subscribe Now".
+           */
+          var popupbox = this._document.getElementById("handlersMenuList")
+                             .firstChild.boxObject;
+          popupbox.QueryInterface(Components.interfaces.nsIPopupBoxObject);
+          if (popupbox.popupState == "hiding" && !this._chooseClientApp()) {
+            // Select the (per-prefs) selected handler if no application was
+            // selected
+            this._setSelectedHandler(this._getFeedType());
+          }
+          break;
+        default:
+          this._setAlwaysUseLabel();
+      }
+    }
+  },
+
+  _setSelectedHandler: function setSelectedHandler(feedType) {
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch);
+    var handler = safeGetCharPref(getPrefReaderForType(feedType), "messenger");
+
+    switch (handler) {
+      case "web":
+        var handlersMenuList = this._document.getElementById("handlersMenuList");
+        if (handlersMenuList) {
+          var url = prefs.getComplexValue(getPrefWebForType(feedType),
+                                          Components.interfaces.nsISupportsString).data;
+          var handlers = handlersMenuList.getElementsByAttribute("webhandlerurl", url);
+          if (handlers.length == 0) {
+            LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist");
+            return;
+          }
+
+          this._safeDoCommand(handlers[0]);
+        }
+        break;
+      case "client":
+        try {
+          this._selectedApp =
+            prefs.getComplexValue(getPrefAppForType(feedType),
+                                  Components.interfaces.nsILocalFile);
+        }
+        catch(ex) {
+          this._selectedApp = null;
+        }
+
+        if (this._selectedApp) {
+          this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
+                                     this._selectedApp);
+          var codeStr = "selectedAppMenuItem.hidden = false; " +
+                        "selectedAppMenuItem.doCommand(); ";
+
+          // Only show the default reader menuitem if the default reader
+          // isn't the selected application
+          if (this._defaultSystemReader) {
+            var shouldHide = this._defaultSystemReader.path == this._selectedApp.path;
+            codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";";
+          }
+          Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+          break;
+        }
+      // fall through if this._selectedApp is null
+      default:
+        var messengerFeedsMenuItem = this._document.getElementById("messengerFeedsMenuItem");
+        if (messengerFeedsMenuItem)
+          this._safeDoCommand(messengerFeedsMenuItem);
+        break;
+    }
+  },
+
+  _initSubscriptionUI: function initSubscriptionUI() {
+    var handlersMenuPopup = this._document.getElementById("handlersMenuPopup");
+    if (!handlersMenuPopup)
+      return;
+
+    var feedType = this._getFeedType();
+    var codeStr;
+
+    // change the background
+    var header = this._document.getElementById("feedHeader");
+    this._contentSandbox.header = header;
+    switch (feedType) {
+      case Components.interfaces.nsIFeed.TYPE_VIDEO:
+        codeStr = "header.className = 'videoPodcastBackground'; ";
+        break;
+
+      case Components.interfaces.nsIFeed.TYPE_AUDIO:
+        codeStr = "header.className = 'audioPodcastBackground'; ";
+        break;
+
+      default:
+        codeStr = "header.className = 'feedBackground'; ";
+    }
+
+
+    // Last-selected application
+    var menuItem = this._document.createElementNS(XUL_NS, "menuitem");
+    menuItem.id = "selectedAppMenuItem";
+    menuItem.className = "menuitem-iconic";
+    menuItem.setAttribute("handlerType", "client");
+    try {
+      var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                            .getService(Components.interfaces.nsIPrefBranch);
+      this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
+                                                Components.interfaces.nsILocalFile);
+
+      if (this._selectedApp.exists())
+        this._initMenuItemWithFile(menuItem, this._selectedApp);
+      else {
+        // Hide the menuitem if the last selected application doesn't exist
+        menuItem.hidden = true;
+      }
+    }
+    catch(ex) {
+      // Hide the menuitem until an application is selected
+      menuItem.hidden = true;
+    }
+    this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
+    this._contentSandbox.selectedAppMenuItem = menuItem;
+
+    codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
+
+    menuItem = null;
+
+#ifdef HAVE_SHELL_SERVICE
+    // List the default feed reader
+    try {
+      this._defaultSystemReader = Components.classes["@mozilla.org/suite/shell-service;1"]
+                                            .getService(Components.interfaces.nsIShellService)
+                                            .defaultFeedReader;
+      menuItem = this._document.createElementNS(XUL_NS, "menuitem");
+      menuItem.id = "defaultHandlerMenuItem";
+      menuItem.className = "menuitem-iconic";
+      menuItem.setAttribute("handlerType", "client");
+
+      this._initMenuItemWithFile(menuItem, this._defaultSystemReader);
+
+      // Hide the default reader item if it points to the same application
+      // as the last-selected application
+      if (this._selectedApp &&
+          this._selectedApp.path == this._defaultSystemReader.path)
+        menuItem.hidden = true;
+    }
+    catch(ex) {
+    }
+#endif
+
+    if (menuItem) {
+      this._contentSandbox.defaultHandlerMenuItem = menuItem;
+      codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
+    }
+
+    // "Choose Application..." menuitem
+    menuItem = this._document.createElementNS(XUL_NS, "menuitem");
+    menuItem.id = "chooseApplicationMenuItem";
+    menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
+
+    this._contentSandbox.chooseAppMenuItem = menuItem;
+    codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
+
+    // separator
+    this._contentSandbox.chooseAppSep = this._document
+                                            .createElementNS(XUL_NS, "menuseparator");
+    codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
+
+    Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+
+    var historySvc = Components.classes["@mozilla.org/browser/nav-history-service;1"]
+                               .getService(Components.interfaces.nsINavHistoryService);
+    historySvc.addObserver(this, false);
+
+    // List of web handlers
+    var wccr = Components.classes["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]
+                         .getService(Components.interfaces.nsIWebContentConverterService);
+    var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType), {});
+    if (handlers.length != 0) {
+      for (let i = 0; i < handlers.length; ++i) {
+        menuItem = this._document.createElementNS(XUL_NS, "menuitem");
+        menuItem.className = "menuitem-iconic";
+        menuItem.setAttribute("label", handlers[i].name);
+        menuItem.setAttribute("handlerType", "web");
+        menuItem.setAttribute("webhandlerurl", handlers[i].uri);
+        this._contentSandbox.menuItem = menuItem;
+        codeStr = "handlersMenuPopup.appendChild(menuItem);";
+        Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+
+        let uri = makeURI(handlers[i].uri);
+        if (!this._setFaviconForWebReader(uri, menuItem)) {
+          if (uri && /^https?/.test(uri.scheme)) {
+            let iconURL = makeURI(uri.resolve("/favicon.ico"));
+            this._faviconService.setAndLoadFaviconForPage(uri, iconURL, true);
+          }
+        }
+      }
+      this._contentSandbox.menuItem = null;
+    }
+
+    this._setSelectedHandler(feedType);
+
+    // "Subscribe using..."
+    this._setSubscribeUsingLabel();
+
+    // "Always use..." checkbox initial state
+    this._setAlwaysUseCheckedState(feedType);
+    this._setAlwaysUseLabel();
+
+    // We update the "Always use.." checkbox label whenever the selected item
+    // in the list is changed
+    handlersMenuPopup.addEventListener("command", this, false);
+
+    // Set up the "Subscribe Now" button
+    this._document
+        .getElementById("subscribeButton")
+        .addEventListener("command", this, false);
+
+    // first-run ui
+    var showFirstRunUI = true;
+    try {
+      showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
+    }
+    catch (ex) {
+    }
+    if (showFirstRunUI) {
+      var textfeedinfo1, textfeedinfo2;
+      switch (feedType) {
+        case Components.interfaces.nsIFeed.TYPE_VIDEO:
+          textfeedinfo1 = "feedSubscriptionVideoPodcast1";
+          textfeedinfo2 = "feedSubscriptionVideoPodcast2";
+          break;
+        case Components.interfaces.nsIFeed.TYPE_AUDIO:
+          textfeedinfo1 = "feedSubscriptionAudioPodcast1";
+          textfeedinfo2 = "feedSubscriptionAudioPodcast2";
+          break;
+        default:
+          textfeedinfo1 = "feedSubscriptionFeed1";
+          textfeedinfo2 = "feedSubscriptionFeed2";
+      }
+
+      this._contentSandbox.feedinfo1 =
+        this._document.getElementById("feedSubscriptionInfo1");
+      this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
+      this._contentSandbox.feedinfo2 =
+        this._document.getElementById("feedSubscriptionInfo2");
+      this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
+      this._contentSandbox.header = header;
+      codeStr = "feedinfo1.textContent = feedinfo1Str; " +
+                "feedinfo2.textContent = feedinfo2Str; " +
+                "header.setAttribute('firstrun', 'true');";
+      Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+      prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
+    }
+  },
+
+  /**
+   * Returns the original URI object of the feed and ensures that this
+   * component is only ever invoked from the preview document.
+   * @param aWindow
+   *        The window of the document invoking the BrowserFeedWriter
+   */
+  _getOriginalURI: function getOriginalURI(aWindow) {
+    var chan = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                      .getInterface(Components.interfaces.nsIWebNavigation)
+                      .QueryInterface(Components.interfaces.nsIDocShell)
+                      .currentDocumentChannel;
+
+    var uri = makeURI(SUBSCRIBE_PAGE_URI);
+    var resolvedURI = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+                                .getService(Components.interfaces.nsIChromeRegistry)
+                                .convertChromeURL(uri);
+
+    if (resolvedURI.equals(chan.URI))
+      return chan.originalURI;
+
+    return null;
+  },
+
+  _window: null,
+  _document: null,
+  _feedURI: null,
+  _feedPrincipal: null,
+
+  // nsIFeedWriter
+  init: function init(aWindow) {
+    // Explicitly wrap |window| in an XPCNativeWrapper to make sure
+    // it's a real native object! This will throw an exception if we
+    // get a non-native object.
+    var window = new XPCNativeWrapper(aWindow);
+    this._feedURI = this._getOriginalURI(window);
+    if (!this._feedURI)
+      return;
+
+    this._window = window;
+    this._document = window.document;
+
+    var secman = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                           .getService(Components.interfaces.nsIScriptSecurityManager);
+    this._feedPrincipal = secman.getCodebasePrincipal(this._feedURI);
+
+    LOG("Subscribe Preview: feed uri = " + this._window.location.href);
+
+    // Set up the subscription UI
+    this._initSubscriptionUI();
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch2);
+    prefs.addObserver(PREF_SELECTED_ACTION, this, false);
+    prefs.addObserver(PREF_SELECTED_READER, this, false);
+    prefs.addObserver(PREF_SELECTED_WEB, this, false);
+    prefs.addObserver(PREF_SELECTED_APP, this, false);
+    prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
+    prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
+    prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
+    prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
+
+    prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
+    prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
+    prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
+    prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
+  },
+
+  writeContent: function writeContent() {
+    if (!this._window)
+      return;
+
+    try {
+      // Set up the feed content
+      var container = this._getContainer();
+      if (!container)
+        return;
+
+      this._setTitleText(container);
+      this._setTitleImage(container);
+      this._writeFeedContent(container);
+    }
+    finally {
+      this._removeFeedFromCache();
+    }
+  },
+
+  close: function close() {
+    this._document
+        .getElementById("handlersMenuPopup")
+        .removeEventListener("command", this, false);
+    this._document
+        .getElementById("subscribeButton")
+        .removeEventListener("command", this, false);
+    this._document = null;
+    this._window = null;
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch2);
+    prefs.removeObserver(PREF_SELECTED_ACTION, this);
+    prefs.removeObserver(PREF_SELECTED_READER, this);
+    prefs.removeObserver(PREF_SELECTED_WEB, this);
+    prefs.removeObserver(PREF_SELECTED_APP, this);
+    prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
+    prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
+    prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
+    prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
+
+    prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
+    prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
+    prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
+    prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
+
+    this._removeFeedFromCache();
+    this.__faviconService = null;
+    this.__bundle = null;
+    this._feedURI = null;
+    this.__contentSandbox = null;
+
+    var historySvc = Components.classes["@mozilla.org/browser/nav-history-service;1"]
+                               .getService(Components.interfaces.nsINavHistoryService);
+    historySvc.removeObserver(this);
+  },
+
+  _removeFeedFromCache: function removeFeedFromCache() {
+    if (this._feedURI) {
+      var feedService = Components.classes["@mozilla.org/browser/feeds/result-service;1"]
+                                  .getService(Components.interfaces.nsIFeedResultService);
+      feedService.removeFeedResult(this._feedURI);
+      this._feedURI = null;
+    }
+  },
+
+  subscribe: function subscribe() {
+    var feedType = this._getFeedType();
+
+    // Subscribe to the feed using the selected handler and save prefs
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefBranch);
+    var defaultHandler = "reader";
+    var useAsDefault = this._document.getElementById("alwaysUse")
+                                     .getAttribute("checked");
+
+    var handlersMenuList = this._document.getElementById("handlersMenuList");
+    var selectedItem = this._getSelectedItemFromMenulist(handlersMenuList);
+
+    // Show the file picker before subscribing if the
+    // choose application menuitem was chosen using the keyboard
+    if (selectedItem.id == "chooseApplicationMenuItem") {
+      if (!this._chooseClientApp())
+        return;
+
+      selectedItem = this._getSelectedItemFromMenulist(handlersMenuList);
+    }
+
+    if (selectedItem.hasAttribute("webhandlerurl")) {
+      var webURI = selectedItem.getAttribute("webhandlerurl");
+      prefs.setCharPref(getPrefReaderForType(feedType), "web");
+
+      var supportsString = Components.classes["@mozilla.org/supports-string;1"]
+                                     .createInstance(Components.interfaces.nsISupportsString);
+      supportsString.data = webURI;
+      prefs.setComplexValue(getPrefWebForType(feedType), Components.interfaces.nsISupportsString,
+                            supportsString);
+
+      var wccr = Components.classes["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]
+                           .getService(Components.interfaces.nsIWebContentConverterService);
+      var handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
+      if (handler) {
+        if (useAsDefault)
+          wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
+
+        this._window.location.href = handler.getHandlerURI(this._window.location.href);
+        return;
+      }
+    }
+    else {
+      switch (selectedItem.id) {
+        case "selectedAppMenuItem":
+          prefs.setComplexValue(getPrefAppForType(feedType), Components.interfaces.nsILocalFile,
+                                this._selectedApp);
+          prefs.setCharPref(getPrefReaderForType(feedType), "client");
+          break;
+        case "defaultHandlerMenuItem":
+          prefs.setComplexValue(getPrefAppForType(feedType), Components.interfaces.nsILocalFile,
+                                this._defaultSystemReader);
+          prefs.setCharPref(getPrefReaderForType(feedType), "client");
+          break;
+        case "messengerFeedsMenuItem":
+          defaultHandler = "messenger";
+          prefs.setCharPref(getPrefReaderForType(feedType), "messenger");
+          break;
+      }
+      var feedService = Components.classes["@mozilla.org/browser/feeds/result-service;1"]
+                                  .getService(Components.interfaces.nsIFeedResultService);
+
+      // Pull the title and subtitle out of the document
+      var feedTitle = this._document.getElementById(TITLE_ID).textContent;
+      var feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
+      feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType);
+    }
+
+    // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
+    // to either "reader" (If a web reader or if an application is selected),
+    // or to "messenger" (if the messenger feeds option is selected).
+    // Otherwise, we should set it to "ask"
+    if (useAsDefault)
+      prefs.setCharPref(getPrefActionForType(feedType), defaultHandler);
+    else
+      prefs.setCharPref(getPrefActionForType(feedType), "ask");
+  },
+
+  // nsIObserver
+  observe: function observe(subject, topic, data) {
+    // see init()
+    subject = new XPCNativeWrapper(subject);
+
+    if (!this._window) {
+      // this._window is null unless this.init was called with a trusted
+      // window object.
+      return;
+    }
+
+    var feedType = this._getFeedType();
+
+    if (topic == "nsPref:changed") {
+      switch (data) {
+        case PREF_SELECTED_READER:
+        case PREF_SELECTED_WEB:
+        case PREF_SELECTED_APP:
+        case PREF_VIDEO_SELECTED_READER:
+        case PREF_VIDEO_SELECTED_WEB:
+        case PREF_VIDEO_SELECTED_APP:
+        case PREF_AUDIO_SELECTED_READER:
+        case PREF_AUDIO_SELECTED_WEB:
+        case PREF_AUDIO_SELECTED_APP:
+          this._setSelectedHandler(feedType);
+          break;
+        case PREF_SELECTED_ACTION:
+        case PREF_VIDEO_SELECTED_ACTION:
+        case PREF_AUDIO_SELECTED_ACTION:
+          this._setAlwaysUseCheckedState(feedType);
+      }
+    }
+  },
+
+  /**
+   * Sets the icon for the given web-reader item in the readers menu
+   * if the favicon-service has the necessary icon stored.
+   * @param aURI
+   *        the reader URI.
+   * @param aMenuItem
+   *        the reader item in the readers menulist.
+   * @return true if the icon was set, false otherwise.
+   */
+  _setFaviconForWebReader: function setFaviconForWebReader(aURI, aMenuItem) {
+    var faviconsSvc = this._faviconService;
+    var faviconURI = null;
+    try {
+      faviconURI = faviconsSvc.getFaviconForPage(aURI);
+    }
+    catch(ex) {
+    }
+
+    if (faviconURI) {
+      var dataURL = faviconsSvc.getFaviconDataAsDataURL(faviconURI);
+      if (dataURL) {
+        this._contentSandbox.menuItem = aMenuItem;
+        this._contentSandbox.dataURL = dataURL;
+        var codeStr = "menuItem.setAttribute('image', dataURL);";
+        Components.utils.evalInSandbox(codeStr, this._contentSandbox);
+        this._contentSandbox.menuItem = null;
+        this._contentSandbox.dataURL = null;
+
+        return true;
+      }
+    }
+
+    return false;
+  },
+
+   // nsINavHistoryService
+   onPageChanged: function onPageChanged(aURI, aWhat, aValue) {
+     // see init()
+     aURI = new XPCNativeWrapper(aURI);
+
+     if (aWhat == Components.interfaces.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
+       // Go through the readers menu and look for the corresponding
+       // reader menu-item for the page if any.
+       var spec = aURI.spec;
+       var handlersMenulist = this._document.getElementById("handlersMenuList");
+       var possibleHandlers = handlersMenulist.firstChild.childNodes;
+       for (let i=0; i < possibleHandlers.length ; i++) {
+         if (possibleHandlers[i].getAttribute("webhandlerurl") == spec) {
+           this._setFaviconForWebReader(aURI, possibleHandlers[i]);
+           return;
+         }
+       }
+     }
+   },
+
+   onBeginUpdateBatch: function() { },
+   onEndUpdateBatch: function() { },
+   onVisit: function() { },
+   onTitleChanged: function() { },
+   onDeleteURI: function() { },
+   onClearHistory: function() { },
+   onPageExpired: function() { },
+
+  // nsIClassInfo
+  getInterfaces: function getInterfaces(countRef) {
+    var interfaces = [Components.interfaces.nsIFeedWriter,
+                       Components.interfaces.nsIClassInfo,
+                       Components.interfaces.nsISupports];
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+
+  getHelperForLanguage: function getHelperForLanguage(language) null,
+  contractID: "@mozilla.org/browser/feeds/result-writer;1",
+  classDescription: "Feed Writer",
+  classID: Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}"),
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: Components.interfaces.nsIClassInfo.DOM_OBJECT,
+  _xpcom_categories: [{ category: "JavaScript global constructor",
+                        entry: "BrowserFeedWriter"}],
+  QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIFeedWriter,
+                                          Components.interfaces.nsIClassInfo,
+                                          Components.interfaces.nsIDOMEventListener,
+                                          Components.interfaces.nsINavHistoryObserver,
+                                          Components.interfaces.nsIObserver])
+
+};
+
+function NSGetModule(cm, file) {
+  return XPCOMUtils.generateModule([FeedWriter]);
+}
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/Makefile.in
@@ -0,0 +1,68 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Feed Handling System
+#
+# The Initial Developer of the Original Code is Google Inc.
+# Portions created by the Initial Developer are Copyright (C) 2006
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#  Ben Goodger <beng@google.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# 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 *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE = suitefeeds
+LIBRARY_NAME = suitefeeds_s
+META_COMPONENT  = suite
+FORCE_STATIC_LIB = 1
+FORCE_USE_PIC = 1
+
+DEFINES += \
+	-DMOZ_APP_NAME=$(MOZ_APP_NAME) \
+	-DMOZ_APP_DISPLAYNAME=$(MOZ_APP_DISPLAYNAME) \
+	$(NULL)
+
+EXTRA_PP_COMPONENTS = \
+	FeedConverter.js \
+	FeedWriter.js \
+	WebContentConverter.js \
+	nsAboutFeeds.js \
+	$(NULL)
+
+REQUIRES = xpcom string necko caps js xpconnect mimetype
+
+CPPSRCS = \
+	nsFeedSniffer.cpp \
+	$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/WebContentConverter.js
@@ -0,0 +1,893 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Web Content Converter System.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Asaf Romano <mano@mozilla.com>
+ *   Dan Mosedale <dmose@mozilla.org>
+ *   Caio Tiago Oliveira <asrail@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/debug.js");
+
+const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
+const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
+const WCCR_CLASSNAME = "Web Content Handler Registrar";
+
+const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
+const WCC_CLASSNAME = "Web Service Handler";
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_ANY = "*/*";
+
+const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
+const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
+const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost";
+
+const STRING_BUNDLE_URI = "chrome://communicator/locale/feeds/subscribe.properties";
+
+const NS_ERROR_MODULE_DOM = 0x80530000;
+const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
+
+function LOG(str) {
+  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                        .getService(Components.interfaces.nsIPrefBranch);
+
+  try {
+    if (prefs.getBoolPref("feeds.log"))
+      dump("*** Feeds: " + str + "\n");
+  }
+  catch (ex) {
+  }
+}
+
+function getNotificationBox(aWindow)
+{
+  return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                .getInterface(Components.interfaces.nsIWebNavigation)
+                .QueryInterface(Components.interfaces.nsIDocShell)
+                .chromeEventHandler.parentNode.wrappedJSObject;
+}
+
+function WebContentConverter() {
+}
+
+WebContentConverter.prototype = {
+  convert: function convert() { },
+  asyncConvertData: function asyncConvertData() { },
+  onDataAvailable: function onDataAvailable() { },
+  onStopRequest: function onStopRequest() { },
+
+  onStartRequest: function onStartRequest(request, context) {
+    var wccr = Components.classes[WCCR_CONTRACTID]
+                         .getService(Components.interfaces.nsIWebContentConverterService);
+    wccr.loadPreferredHandler(request);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI(
+    [Components.interfaces.nsIStreamConverter,
+     Components.interfaces.nsIStreamListener,
+     Components.interfaces.nsISupports])
+};
+
+var WebContentConverterFactory = {
+  createInstance: function createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return new WebContentConverter().QueryInterface(iid);
+  },
+
+  QueryInterface: XPCOMUtils.generateQI(
+    [Components.interfaces.nsIFactory,
+     Components.interfaces.nsISupports])
+};
+
+function ServiceInfo(contentType, uri, name) {
+  this._contentType = contentType;
+  this._uri = uri;
+  this._name = name;
+}
+
+ServiceInfo.prototype = {
+  /**
+   * See nsIHandlerApp
+   */
+  get name() {
+    return this._name;
+  },
+
+  /**
+   * See nsIHandlerApp
+   */
+  equals: function equals(aHandlerApp) {
+    if (!aHandlerApp)
+      throw Components.results.NS_ERROR_NULL_POINTER;
+
+    if (aHandlerApp instanceof Components.interfaces.nsIWebContentHandlerInfo &&
+        aHandlerApp.contentType == this.contentType &&
+        aHandlerApp.uri == this.uri)
+      return true;
+
+    return false;
+  },
+
+  /**
+   * See nsIWebContentHandlerInfo
+   */
+  get contentType() {
+    return this._contentType;
+  },
+
+  /**
+   * See nsIWebContentHandlerInfo
+   */
+  get uri() {
+    return this._uri;
+  },
+
+  /**
+   * See nsIWebContentHandlerInfo
+   */
+  getHandlerURI: function getHandlerURI(uri) {
+    return this._uri.replace(/%s/gi, encodeURIComponent(uri));
+  },
+
+  QueryInterface: XPCOMUtils.generateQI(
+    [Components.interfaces.nsIWebContentHandlerInfo,
+     Components.interfaces.nsISupports])
+};
+
+function WebContentConverterRegistrar() {
+  this._contentTypes = { };
+  this._autoHandleContentTypes = { };
+}
+
+WebContentConverterRegistrar.prototype = {
+  __bundle: null,
+  get _bundle() {
+    if (!this.__bundle) {
+      this.__bundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
+                        .getService(Components.interfaces.nsIStringBundleService)
+                        .createBundle(STRING_BUNDLE_URI);
+    }
+    return this.__bundle;
+  },
+
+  _getFormattedString: function getFormattedString(key, params) {
+    return this._bundle.formatStringFromName(key, params, params.length);
+  },
+
+  _getString: function getString(key) {
+    try {
+      return this._bundle.GetStringFromName(key);
+    } catch(e) {
+      LOG("Couldn't retrieve key from bundle");
+    }
+
+    return null;
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  getAutoHandler: function getAutoHandler(contentType) {
+    contentType = this._resolveContentType(contentType);
+    if (contentType in this._autoHandleContentTypes)
+      return this._autoHandleContentTypes[contentType];
+    return null;
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  setAutoHandler: function setAutoHandler(contentType, handler) {
+    if (handler && !this._typeIsRegistered(contentType, handler.uri))
+      throw Components.results.NS_ERROR_NOT_AVAILABLE;
+
+    contentType = this._resolveContentType(contentType);
+    this._setAutoHandler(contentType, handler);
+
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefService);
+    var autoBranch = prefs.getBranch(PREF_CONTENTHANDLERS_AUTO);
+    if (handler)
+      autoBranch.setCharPref(contentType, handler.uri);
+    else if (autoBranch.prefHasUserValue(contentType))
+      autoBranch.clearUserPref(contentType);
+
+    prefs.savePrefFile(null);
+  },
+
+  /**
+   * Update the internal data structure (not persistent)
+   */
+  _setAutoHandler: function setAutoHandler(contentType, handler) {
+    if (handler)
+      this._autoHandleContentTypes[contentType] = handler;
+    else if (contentType in this._autoHandleContentTypes)
+      delete this._autoHandleContentTypes[contentType];
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  getWebContentHandlerByURI: function getWebContentHandlerByURI(contentType, uri) {
+    var handlers = this.getContentHandlers(contentType, { });
+    for (let i = 0; i < handlers.length; ++i) {
+      if (handlers[i].uri == uri)
+        return handlers[i];
+    }
+    return null;
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  loadPreferredHandler: function loadPreferredHandler(request) {
+    var channel = request.QueryInterface(Components.interfaces.nsIChannel);
+    var contentType = this._resolveContentType(channel.contentType);
+    var handler = this.getAutoHandler(contentType);
+    if (handler) {
+      request.cancel(Components.results.NS_ERROR_FAILURE);
+
+      var webNavigation = channel.notificationCallbacks
+                                 .getInterface(Components.interfaces.nsIWebNavigation);
+      webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec),
+                            Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE,
+                            null, null, null);
+    }
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  removeProtocolHandler: function removeProtocolHandler(aProtocol, aURITemplate) {
+    var eps = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+                        .getService(Components.interfaces.nsIExternalProtocolService);
+    var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
+    var handlers =  handlerInfo.possibleApplicationHandlers;
+    for (let i = 0; i < handlers.length; i++) {
+      try { // We only want to test web handlers
+        let handler = handlers.queryElementAt(i, Components.interfaces.nsIWebHandlerApp);
+        if (handler.uriTemplate == aURITemplate) {
+          handlers.removeElementAt(i);
+          let hs = Components.classes["@mozilla.org/uriloader/handler-service;1"]
+                             .getService(Components.interfaces.nsIHandlerService);
+          hs.store(handlerInfo);
+          return;
+        }
+      } catch (e) {
+        /* it wasn't a web handler */
+      }
+    }
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  removeContentHandler: function removeContentHandler(contentType, uri) {
+    function notURI(serviceInfo) {
+      return serviceInfo.uri != uri;
+    }
+
+    if (contentType in this._contentTypes) {
+      this._contentTypes[contentType] = this._contentTypes[contentType]
+                                            .filter(notURI);
+    }
+  },
+
+  /**
+   *
+   */
+  _mappings: {
+    "application/rss+xml": TYPE_MAYBE_FEED,
+    "application/atom+xml": TYPE_MAYBE_FEED,
+  },
+
+  /**
+   * These are types for which there is a separate content converter aside
+   * from our built in generic one. We should not automatically register
+   * a factory for creating a converter for these types.
+   */
+  _blockedTypes: {
+    "application/vnd.mozilla.maybe.feed": true,
+  },
+
+  /**
+   * Determines the "internal" content type based on the _mappings.
+   * @param   contentType
+   * @returns The resolved contentType value.
+   */
+  _resolveContentType: function resolveContentType(contentType) {
+    if (contentType in this._mappings)
+      return this._mappings[contentType];
+    return contentType;
+  },
+
+  _makeURI: function(aURL, aOriginCharset, aBaseURI) {
+    var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+                              .getService(Components.interfaces.nsIIOService);
+    return ioService.newURI(aURL, aOriginCharset, aBaseURI);
+  },
+
+  _checkAndGetURI: function checkAndGetURI(aURIString, aContentWindow) {
+    try {
+      var uri = this._makeURI(aURIString);
+    } catch (ex) {
+      // not supposed to throw according to spec
+      return;
+    }
+
+    // For security reasons we reject non-http(s) urls (see bug 354316),
+    // we may need to revise this once we support more content types
+    // XXX this should be a "security exception" according to spec, but that
+    // isn't defined yet.
+    if (uri.scheme != "http" && uri.scheme != "https")
+      throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
+
+    // We also reject handlers registered from a different host (see bug 402287)
+    // The pref allows us to test the feature
+    var pb = Components.classes["@mozilla.org/preferences-service;1"]
+                       .getService(Components.interfaces.nsIPrefBranch);
+    if ((!pb.prefHasUserValue(PREF_ALLOW_DIFFERENT_HOST) ||
+         !pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST)) &&
+        aContentWindow.location.hostname != uri.host)
+      throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
+
+    // If the uri doesn't contain '%s', it won't be a good handler
+    if (uri.spec.indexOf("%s") < 0)
+      throw NS_ERROR_DOM_SYNTAX_ERR;
+
+    return uri;
+  },
+
+  /**
+   * Determines if a web handler is already registered.
+   *
+   * @param aProtocol
+   *        The scheme of the web handler we are checking for.
+   * @param aURITemplate
+   *        The URI template that the handler uses to handle the protocol.
+   * @return true if it is already registered, false otherwise.
+   */
+  _protocolHandlerRegistered: function protocolHandlerRegistered(aProtocol, aURITemplate) {
+    var eps = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+                        .getService(Components.interfaces.nsIExternalProtocolService);
+    var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
+    var handlers =  handlerInfo.possibleApplicationHandlers;
+    for (let i = 0; i < handlers.length; i++) {
+      try { // We only want to test web handlers
+        let handler = handlers.queryElementAt(i, Components.interfaces.nsIWebHandlerApp);
+        if (handler.uriTemplate == aURITemplate)
+          return true;
+      } catch (e) { /* it wasn't a web handler */ }
+    }
+    return false;
+  },
+
+  /**
+   * See nsIWebContentHandlerRegistrar
+   */
+  registerProtocolHandler: function registerProtocolHandler(aProtocol, aURIString,
+                                                             aTitle, aContentWindow) {
+    LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
+
+    // First, check to make sure this isn't already handled internally (we don't
+    // want to let them take over, say "chrome").
+    var ios = Components.classes["@mozilla.org/network/io-service;1"]
+                        .getService(Components.interfaces.nsIIOService);
+    var handler = ios.getProtocolHandler(aProtocol);
+    if (!(handler instanceof Components.interfaces.nsIExternalProtocolHandler)) {
+      // This is handled internally, so we don't want them to register
+      // XXX this should be a "security exception" according to spec, but that
+      // isn't defined yet.
+      throw("Permission denied to add " + aURIString + " as a protocol handler");
+    }
+
+    // check if it is in the black list
+    var pb = Components.classes["@mozilla.org/preferences-service;1"]
+                       .getService(Components.interfaces.nsIPrefBranch);
+    var allowed;
+    try {
+      allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol);
+    }
+    catch (e) {
+      allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default");
+    }
+    if (!allowed) {
+      // XXX this should be a "security exception" according to spec
+      throw("Not allowed to register a protocol handler for " + aProtocol);
+    }
+
+    var uri = this._checkAndGetURI(aURIString, aContentWindow);
+
+    var buttons, message;
+    if (this._protocolHandlerRegistered(aProtocol, uri.spec))
+      message = this._getFormattedString("protocolHandlerRegistered",
+                                         [aTitle, aProtocol]);
+    else {
+      // Now Ask the user and provide the proper callback
+      message = this._getFormattedString("addProtocolHandler",
+                                         [aTitle, uri.host, aProtocol]);
+      var fis = Components.classes["@mozilla.org/browser/favicon-service;1"]
+                          .getService(Components.interfaces.nsIFaviconService);
+      var notificationIcon = fis.getFaviconLinkForIcon(uri);
+      var notificationValue = "Protocol Registration: " + aProtocol;
+      var addButton = {
+        label: this._getString("addProtocolHandlerAddButton"),
+        accessKey: this._getString("addHandlerAddButtonAccesskey"),
+        protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
+
+        callback: function addProtocolHandlerButtonCallback(aNotification, aButtonInfo) {
+          var protocol = aButtonInfo.protocolInfo.protocol;
+          var uri      = aButtonInfo.protocolInfo.uri;
+          var name     = aButtonInfo.protocolInfo.name;
+
+          var handler = Components.classes["@mozilla.org/uriloader/web-handler-app;1"]
+                                  .createInstance(Components.interfaces.nsIWebHandlerApp);
+          handler.name = name;
+          handler.uriTemplate = uri;
+
+          var eps = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
+                              .getService(Components.interfaces.nsIExternalProtocolService);
+          var handlerInfo = eps.getProtocolHandlerInfo(protocol);
+          handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
+
+          // Since the user has agreed to add a new handler, chances are good
+          // that the next time they see a handler of this type, they're going
+          // to want to use it.  Reset the handlerInfo to ask before the next
+          // use.
+          handlerInfo.alwaysAskBeforeHandling = true;
+
+          var hs = Components.classes["@mozilla.org/uriloader/handler-service;1"]
+                             .getService(Components.interfaces.nsIHandlerService);
+          hs.store(handlerInfo);
+        }
+      };
+      buttons = [addButton];
+    }
+
+    var notificationBox = getNotificationBox(browserElement);
+    notificationBox.appendNotification(message,
+                                       notificationValue,
+                                       notificationIcon,
+                                       notificationBox.PRIORITY_INFO_LOW,
+                                       buttons);
+  },
+
+  /**
+   * See nsIWebContentHandlerRegistrar
+   * If a DOM window is provided, then the request came from content, so we
+   * prompt the user to confirm the registration.
+   */
+  registerContentHandler: function registerContentHandler(aContentType, aURIString,
+                                                           aTitle, aContentWindow) {
+    LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
+
+    // We only support feed types at present.
+    // XXX this should be a "security exception" according to spec, but that
+    // isn't defined yet.
+    var contentType = this._resolveContentType(aContentType);
+    if (contentType != TYPE_MAYBE_FEED)
+      return;
+
+    if (aContentWindow) {
+      var uri = this._checkAndGetURI(aURIString, aContentWindow);
+
+      this._appendFeedReaderNotification(uri, aTitle, getNotificationBox(browserElement));
+    }
+    else
+      this._registerContentHandler(contentType, aURIString, aTitle);
+  },
+
+  /**
+   * Appends a notifcation for the given feed reader details.
+   *
+   * The notification could be either a pseudo-dialog which lets
+   * the user to add the feed reader:
+   * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader?  (Add) [x] ]
+   *
+   * or a simple message for the case where the feed reader is already registered:
+   * [ [icon] %feed-reader-name% is already registered as a Feed Reader             [x] ]
+   *
+   * A new notification isn't appended if the given notificationbox has a
+   * notification for the same feed reader.
+   *
+   * @param aURI
+   *        The url of the feed reader as a nsIURI object
+   * @param aName
+   *        The feed reader name as it was passed to registerContentHandler
+   * @param aNotificationBox
+   *        The notification box to which a notification might be appended
+   * @return true if a notification has been appended, false otherwise.
+   */
+  _appendFeedReaderNotification: function appendFeedReaderNotification(aURI, aName, aNotificationBox) {
+    var uriSpec = aURI.spec;
+    var notificationValue = "feed reader notification: " + uriSpec;
+    var notificationIcon = aURI.prePath + "/favicon.ico";
+
+    // Don't append a new notification if the notificationbox
+    // has a notification for the given feed reader already
+    if (aNotificationBox.getNotificationWithValue(notificationValue))
+      return false;
+
+    var buttons, message;
+    if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
+      message = this._getFormattedString("handlerRegistered", [aName]);
+    else {
+      message = this._getFormattedString("addHandler", [aName, aURI.host]);
+      var self = this;
+      var addButton = {
+        _outer: self,
+        label: self._getString("addHandlerAddButton"),
+        accessKey: self._getString("addHandlerAddButtonAccesskey"),
+        feedReaderInfo: { uri: uriSpec, name: aName },
+
+        /* static */
+        callback: function addFeedReaderButtonCallback(aNotification, aButtonInfo) {
+          var uri = aButtonInfo.feedReaderInfo.uri;
+          var name = aButtonInfo.feedReaderInfo.name;
+          var outer = aButtonInfo._outer;
+
+          // The reader could have been added from another window mean while
+          if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
+            outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
+
+          // avoid reference cycles
+          aButtonInfo._outer = null;
+
+          return false;
+        }
+      };
+      buttons = [addButton];
+    }
+
+    aNotificationBox.appendNotification(message,
+                                        notificationValue,
+                                        notificationIcon,
+                                        aNotificationBox.PRIORITY_INFO_LOW,
+                                        buttons);
+    return true;
+  },
+
+  /**
+   * Save Web Content Handler metadata to persistent preferences.
+   * @param   contentType
+   *          The content Type being handled
+   * @param   uri
+   *          The uri of the web service
+   * @param   title
+   *          The human readable name of the web service
+   *
+   * This data is stored under:
+   *
+   *    browser.contentHandlers.type0 = content/type
+   *    browser.contentHandlers.uri0 = http://www.foo.com/q=%s
+   *    browser.contentHandlers.title0 = Foo 2.0alphr
+   */
+  _saveContentHandlerToPrefs: function saveContentHandlerToPrefs(contentType, uri, title) {
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefService);
+    var i = 0;
+    var typeBranch = null;
+    while (true) {
+      typeBranch = prefs.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
+      try {
+        typeBranch.getCharPref("type");
+        ++i;
+      }
+      catch (e) {
+        // No more handlers
+        break;
+      }
+    }
+    if (typeBranch) {
+      typeBranch.setCharPref("type", contentType);
+      var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
+                          .createInstance(Components.interfaces.nsIPrefLocalizedString);
+      pls.data = uri;
+      typeBranch.setComplexValue("uri",
+                                 Components.interfaces.nsIPrefLocalizedString, pls);
+      pls.data = title;
+      typeBranch.setComplexValue("title",
+                                 Components.interfaces.nsIPrefLocalizedString, pls);
+
+      prefs.savePrefFile(null);
+    }
+  },
+
+  /**
+   * Determines if there is a type with a particular uri registered for the
+   * specified content type already.
+   * @param   contentType
+   *          The content type that the uri handles
+   * @param   uri
+   *          The uri of the
+   */
+  _typeIsRegistered: function typeIsRegistered(contentType, uri) {
+    if (!(contentType in this._contentTypes))
+      return false;
+
+    var services = this._contentTypes[contentType];
+    for (let i = 0; i < services.length; ++i) {
+      // This uri has already been registered
+      if (services[i].uri == uri)
+        return true;
+    }
+    return false;
+  },
+
+  /**
+   * Gets a stream converter contract id for the specified content type.
+   * @param   contentType
+   *          The source content type for the conversion.
+   * @returns A contract id to construct a converter to convert between the
+   *          contentType and *\/*.
+   */
+  _getConverterContractID: function getConverterContractID(contentType) {
+    const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
+    return template.replace(/%s/, contentType);
+  },
+
+  /**
+   * Register a web service handler for a content type.
+   *
+   * @param   contentType
+   *          the content type being handled
+   * @param   uri
+   *          the URI of the web service
+   * @param   title
+   *          the human readable name of the web service
+   */
+  _registerContentHandler: function registerContentHandler(contentType, uri, title) {
+    this._updateContentTypeHandlerMap(contentType, uri, title);
+    this._saveContentHandlerToPrefs(contentType, uri, title);
+
+    if (contentType == TYPE_MAYBE_FEED) {
+      // Make the new handler the last-selected reader in the preview page
+      // and make sure the preview page is shown the next time a feed is visited
+      var pb = Components.classes["@mozilla.org/preferences-service;1"]
+                         .getService(Components.interfaces.nsIPrefService).getBranch(null);
+      pb.setCharPref(PREF_SELECTED_READER, "web");
+
+      var supportsString = Components.classes["@mozilla.org/supports-string;1"]
+                                     .createInstance(Components.interfaces.nsISupportsString);
+      supportsString.data = uri;
+      pb.setComplexValue(PREF_SELECTED_WEB, Components.interfaces.nsISupportsString,
+                         supportsString);
+      pb.setCharPref(PREF_SELECTED_ACTION, "ask");
+      this._setAutoHandler(TYPE_MAYBE_FEED, null);
+    }
+  },
+
+  /**
+   * Update the content type -> handler map. This mapping is not persisted, use
+   * registerContentHandler or _saveContentHandlerToPrefs for that purpose.
+   * @param   contentType
+   *          The content Type being handled
+   * @param   uri
+   *          The uri of the web service
+   * @param   title
+   *          The human readable name of the web service
+   */
+  _updateContentTypeHandlerMap: function updateContentTypeHandlerMap(contentType, uri, title) {
+    if (!(contentType in this._contentTypes))
+      this._contentTypes[contentType] = [];
+
+    // Avoid adding duplicates
+    if (this._typeIsRegistered(contentType, uri))
+      return;
+
+    this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
+
+    if (!(contentType in this._blockedTypes)) {
+      var converterContractID = this._getConverterContractID(contentType);
+      var cr = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+      cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
+                         WebContentConverterFactory);
+    }
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  getContentHandlers: function getContentHandlers(contentType, countRef) {
+    countRef.value = 0;
+    if (!(contentType in this._contentTypes))
+      return [];
+
+    var handlers = this._contentTypes[contentType];
+    countRef.value = handlers.length;
+    return handlers;
+  },
+
+  /**
+   * See nsIWebContentConverterService
+   */
+  resetHandlersForType: function resetHandlersForType(contentType) {
+    // currently unused within the tree, so only useful for extensions; previous
+    // impl. was buggy (and even infinite-looped!), so I argue that this is a
+    // definite improvement
+    throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+  },
+
+  /**
+   * Registers a handler from the settings on a preferences branch.
+   *
+   * @param branch
+   *        an nsIPrefBranch containing "type", "uri", and "title" preferences
+   *        corresponding to the content handler to be registered
+   */
+  _registerContentHandlerWithBranch: function(branch) {
+    /**
+     * Since we support up to six predefined readers, we need to handle gaps
+     * better, since the first branch with user-added values will be .6
+     *
+     * How we deal with that is to check to see if there's no prefs in the
+     * branch and stop cycling once that's true.  This doesn't fix the case
+     * where a user manually removes a reader, but that's not supported yet!
+     */
+    var vals = branch.getChildList("", {});
+    if (vals.length == 0)
+      return;
+
+    try {
+      var type = branch.getCharPref("type");
+      var uri = branch.getComplexValue("uri", Components.interfaces.nsIPrefLocalizedString).data;
+      var title = branch.getComplexValue("title",
+                                         Components.interfaces.nsIPrefLocalizedString).data;
+      this._updateContentTypeHandlerMap(type, uri, title);
+    }
+    catch(ex) {
+      // do nothing, the next branch might have values
+    }
+  },
+
+  /**
+   * Load the auto handler, content handler and protocol tables from
+   * preferences.
+   */
+  _init: function init() {
+    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                          .getService(Components.interfaces.nsIPrefService);
+
+    var kids = prefs.getBranch(PREF_CONTENTHANDLERS_BRANCH)
+                    .getChildList("", {});
+    // first get the numbers of the providers by getting all ###.uri prefs
+    var nums = [];
+    for (let i = 0; i < kids.length; i++) {
+      let match = /^(\d+)\.uri$/.exec(kids[i]);
+      if (match)
+        nums.push(match[1]);
+    }
+    // sort them, to get them back in order
+    nums.sort(function(a, b) {return a - b;});
+    // now register them
+    for (let i = 0; i < nums.length; i++) {
+      let branch = prefs.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + ".");
+      this._registerContentHandlerWithBranch(branch);
+    }
+
+    // We need to do this _after_ registering all of the available handlers,
+    // so that getWebContentHandlerByURI can return successfully.
+    try {
+      var autoBranch = prefs.getBranch(PREF_CONTENTHANDLERS_AUTO);
+      var childPrefs = autoBranch.getChildList("", { });
+      for (let i = 0; i < childPrefs.length; ++i) {
+        let type = childPrefs[i];
+        let uri = autoBranch.getCharPref(type);
+        if (uri) {
+          let handler = this.getWebContentHandlerByURI(type, uri);
+          this._setAutoHandler(type, handler);
+        }
+      }
+    }
+    catch (e) {
+    }
+  },
+
+  /**
+   * See nsIObserver
+   */
+  observe: function observe(subject, topic, data) {
+    var os = Components.classes["@mozilla.org/observer-service;1"]
+                       .getService(Components.interfaces.nsIObserverService);
+    switch (topic) {
+    case "app-startup":
+      os.addObserver(this, "final-ui-startup", false);
+      break;
+    case "final-ui-startup":
+      os.removeObserver(this, "final-ui-startup");
+      this._init();
+      break;
+    }
+  },
+
+  /**
+   * See nsIFactory
+   */
+  createInstance: function createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return this.QueryInterface(iid);
+  },
+
+  /**
+   * See nsIClassInfo
+   */
+  getInterfaces: function getInterfaces(countRef) {
+    var interfaces = [Components.interfaces.nsIWebContentConverterService,
+                      Components.interfaces.nsIWebContentHandlerRegistrar,
+                      Components.interfaces.nsIObserver,
+                      Components.interfaces.nsIClassInfo,
+                      Components.interfaces.nsIFactory,
+                      Components.interfaces.nsISupports];
+    countRef.value = interfaces.length;
+    return interfaces;
+  },
+
+ getHelperForLanguage: function getHelperForLanguage(language) {
+    return null;
+  },
+
+  contractID: WCCR_CONTRACTID,
+  classDescription: WCCR_CLASSNAME,
+  classID: WCCR_CLASSID,
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
+  flags: Components.interfaces.nsIClassInfo.DOM_OBJECT,
+
+  /**
+   * See nsISupports
+   */
+  QueryInterface: XPCOMUtils.generateQI(
+    [Components.interfaces.nsIWebContentConverterService,
+     Components.interfaces.nsIWebContentHandlerRegistrar,
+     Components.interfaces.nsIObserver,
+     Components.interfaces.nsIClassInfo,
+     Components.interfaces.nsIFactory,
+     Components.interfaces.nsISupports]),
+
+  _xpcom_categories: [{
+    category: "app-startup",
+    service: true
+  }]
+};
+
+function NSGetModule(cm, file) {
+  return XPCOMUtils.generateModule([WebContentConverterRegistrar]);
+}
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/nsAboutFeeds.js
@@ -0,0 +1,80 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the SeaMonkey internet suite code.
+ *
+ * The Initial Developer of the Original Code is
+ * Caio Tiago Oliveira <asrail@gmail.com>
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+const ABOUTFEEDS_URI = "chrome://communicator/content/feeds/subscribe.xhtml";
+const ABOUTFEEDS_CONTRACTID = "@mozilla.org/network/protocol/about;1?what=feeds";
+const ABOUTFEEDS_CLASSNAME = "About Feeds Page";
+const ABOUTFEEDS_CLASSID = Components.ID("{f3487aac-65a0-4101-88a4-f7450c231351}");
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function AboutFeeds() {
+}
+
+AboutFeeds.prototype = {
+  classDescription: ABOUTFEEDS_CLASSNAME,
+  contractID: ABOUTFEEDS_CONTRACTID,
+  classID: ABOUTFEEDS_CLASSID,
+  implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
+  QueryInterface: XPCOMUtils.generateQI(
+    [Components.interfaces.nsIAboutModule,
+    Components.interfaces.nsISupports]),
+
+  newChannel: function(aURI) {
+    var ios = Components.classes["@mozilla.org/network/io-service;1"]
+                        .getService(Components.interfaces.nsIIOService);
+    var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
+                           .getService(Components.interfaces.nsIScriptSecurityManager);
+    var channel = ios.newChannel(ABOUTFEEDS_URI, null, null);
+    var principal = secMan.getCodebasePrincipal(aURI);
+
+    channel.originalURI = aURI;
+    channel.owner = principal;
+
+    return channel;
+  },
+
+  getURIFlags: function() {
+    return Components.interfaces.nsIAboutModule.ALLOW_SCRIPT |
+           Components.interfaces.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
+  },
+
+};
+
+function NSGetModule(cm, file) {
+  return XPCOMUtils.generateModule([AboutFeeds]);
+}
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/nsFeedSniffer.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Content Sniffer.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *   Robert Sayre <sayrer@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+#include "nsFeedSniffer.h"
+
+#include "prmem.h"
+
+#include "nsNetCID.h"
+#include "nsXPCOM.h"
+#include "nsCOMPtr.h"
+#include "nsStringStream.h"
+
+#include "nsICategoryManager.h"
+#include "nsIServiceManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIStreamConverterService.h"
+#include "nsIStreamConverter.h"
+
+#include "nsIStreamListener.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIMIMEHeaderParam.h"
+
+#include "nsMimeTypes.h"
+
+#define TYPE_ATOM "application/atom+xml"
+#define TYPE_RSS "application/rss+xml"
+#define TYPE_MAYBE_FEED "application/vnd.mozilla.maybe.feed"
+
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RSS "http://purl.org/rss/1.0/"
+
+#define MAX_BYTES 512
+
+NS_IMPL_ISUPPORTS3(nsFeedSniffer,
+                   nsIContentSniffer,
+                   nsIStreamListener,
+                   nsIRequestObserver)
+
+nsresult
+nsFeedSniffer::ConvertEncodedData(nsIRequest* request,
+                                  const PRUint8* data,
+                                  PRUint32 length)
+{
+  nsresult rv = NS_OK;
+
+ mDecodedData = "";
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+  if (!httpChannel)
+    return NS_ERROR_NO_INTERFACE;
+
+  nsCAutoString contentEncoding;
+  httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
+                                 contentEncoding);
+  if (!contentEncoding.IsEmpty()) {
+    nsCOMPtr<nsIStreamConverterService> converterService(do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID));
+    if (converterService) {
+      ToLowerCase(contentEncoding);
+
+      nsCOMPtr<nsIStreamListener> converter;
+      rv = converterService->AsyncConvertData(contentEncoding.get(),
+                                              "uncompressed", this, nsnull,
+                                              getter_AddRefs(converter));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      converter->OnStartRequest(request, nsnull);
+
+      nsCOMPtr<nsIStringInputStream> rawStream =
+        do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
+      if (!rawStream)
+        return NS_ERROR_FAILURE;
+
+      rv = rawStream->SetData((const char*)data, length);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      rv = converter->OnDataAvailable(request, nsnull, rawStream, 0, length);
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      converter->OnStopRequest(request, nsnull, NS_OK);
+    }
+  }
+  return rv;
+}
+
+template<int N>
+static PRBool
+StringBeginsWithLowercaseLiteral(nsAString& aString,
+                                 const char (&aSubstring)[N])
+{
+  return StringHead(aString, N).LowerCaseEqualsLiteral(aSubstring);
+}
+
+// XXXsayrer put this in here to get on the branch with minimal delay.
+// Trunk really needs to factor this out. This is the third usage.
+PRBool
+HasAttachmentDisposition(nsIHttpChannel* httpChannel)
+{
+  if (!httpChannel)
+    return PR_FALSE;
+
+  nsCAutoString contentDisposition;
+  nsresult rv =
+    httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("content-disposition"),
+                                   contentDisposition);
+
+  if (NS_SUCCEEDED(rv) && !contentDisposition.IsEmpty()) {
+    nsCOMPtr<nsIURI> uri;
+    httpChannel->GetURI(getter_AddRefs(uri));
+    nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =
+      do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);
+    if (NS_SUCCEEDED(rv))
+    {
+      nsCAutoString fallbackCharset;
+      if (uri)
+        uri->GetOriginCharset(fallbackCharset);
+      nsAutoString dispToken;
+      // Get the disposition type
+      rv = mimehdrpar->GetParameter(contentDisposition, "", fallbackCharset,
+                                    PR_TRUE, nsnull, dispToken);
+      // RFC 2183, section 2.8 says that an unknown disposition
+      // value should be treated as "attachment"
+      // XXXbz this code is duplicated in GetFilenameAndExtensionFromChannel in
+      // nsExternalHelperAppService.  Factor it out!
+      if (NS_FAILED(rv) ||
+          (!dispToken.IsEmpty() &&
+           !StringBeginsWithLowercaseLiteral(dispToken, "inline") &&
+           // Broken sites just send
+           // Content-Disposition: filename="file"
+           // without a disposition token... screen those out.
+           !StringBeginsWithLowercaseLiteral(dispToken, "filename") &&
+           // Also in use is Content-Disposition: name="file"
+           !StringBeginsWithLowercaseLiteral(dispToken, "name")))
+        // We have a content-disposition of "attachment" or unknown
+        return PR_TRUE;
+    }
+  }
+
+  return PR_FALSE;
+}
+
+/**
+ * @return the first occurrence of a character within a string buffer,
+ *         or nsnull if not found
+ */
+inline const char*
+FindChar(char c, const char *begin, const char *end)
+{
+  return static_cast<const char *>(memchr(begin, c, end - begin));
+}
+
+/**
+ *
+ * Determine if a substring is the "documentElement" in the document.
+ *
+ * All of our sniffed substrings: <rss, <feed, <rdf:RDF must be the "document"
+ * element within the XML DOM, i.e. the root container element. Otherwise,
+ * it's possible that someone embedded one of these tags inside a document of
+ * another type, e.g. a HTML document, and we don't want to show the preview
+ * page if the document isn't actually a feed.
+ *
+ * @param   start
+ *          The beginning of the data being sniffed
+ * @param   end
+ *          The end of the data being sniffed, right before the substring that
+ *          was found.
+ * @returns PR_TRUE if the found substring is the documentElement, PR_FALSE
+ *          otherwise.
+ */
+static PRBool
+IsDocumentElement(const char *start, const char* end)
+{
+  // For every tag in the buffer, check to see if it's a PI, Doctype or
+  // comment, our desired substring or something invalid.
+  while ( (start = FindChar('<', start, end)) ) {
+    ++start;
+    if (start >= end)
+      return PR_FALSE;
+
+    // Check to see if the character following the '<' is either '?' or '!'
+    // (processing instruction or doctype or comment)... these are valid nodes
+    // to have in the prologue.
+    if (*start != '?' && *start != '!')
+      return PR_FALSE;
+
+    // Now advance the iterator until the '>' (We do this because we don't want
+    // to sniff indicator substrings that are embedded within other nodes, e.g.
+    // comments: <!-- <rdf:RDF .. > -->
+    start = FindChar('>', start, end);
+    if (!start)
+      return PR_FALSE;
+
+    ++start;
+  }
+  return PR_TRUE;
+}
+
+/**
+ * Determines whether or not a string exists as the root element in an XML data
+ * string buffer.
+ * @param   dataString
+ *          The data being sniffed
+ * @param   substring
+ *          The substring being tested for existence and root-ness.
+ * @returns PR_TRUE if the substring exists and is the documentElement, PR_FALSE
+ *          otherwise.
+ */
+static PRBool
+ContainsTopLevelSubstring(nsACString& dataString, const char *substring)
+{
+  PRInt32 offset = dataString.Find(substring);
+  if (offset == -1)
+    return PR_FALSE;
+
+  const char *begin = dataString.BeginReading();
+
+  // Only do the validation when we find the substring.
+  return IsDocumentElement(begin, begin + offset);
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::GetMIMETypeFromContent(nsIRequest* request,
+                                      const PRUint8* data,
+                                      PRUint32 length,
+                                      nsACString& sniffedType)
+{
+  nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
+  if (!channel)
+    return NS_ERROR_NO_INTERFACE;
+
+  // Check that this is a GET request, since you can't subscribe to a POST...
+  nsCAutoString method;
+  channel->GetRequestMethod(method);
+  if (!method.Equals("GET")) {
+    sniffedType.Truncate();
+    return NS_OK;
+  }
+
+  // We need to find out if this is a load of a view-source document. In this
+  // case we do not want to override the content type, since the source display
+  // does not need to be converted from feed format to XUL. More importantly,
+  // we don't want to change the content type from something
+  // nsContentDLF::CreateInstance knows about (e.g. application/xml, text/html
+  // etc) to something that only the application fe knows about (maybe.feed)
+  // thus deactivating syntax highlighting.
+  nsCOMPtr<nsIURI> originalURI;
+  channel->GetOriginalURI(getter_AddRefs(originalURI));
+
+  nsCAutoString scheme;
+  originalURI->GetScheme(scheme);
+  if (scheme.EqualsLiteral("view-source")) {
+    sniffedType.Truncate();
+    return NS_OK;
+  }
+
+  // Check the Content-Type to see if it is set correctly. If it is set to
+  // something specific that we think is a reliable indication of a feed, don't
+  // bother sniffing since we assume the site maintainer knows what they're
+  // doing.
+  nsCAutoString contentType;
+  channel->GetContentType(contentType);
+  PRBool noSniff = contentType.EqualsLiteral(TYPE_RSS) ||
+                   contentType.EqualsLiteral(TYPE_ATOM);
+
+  // Check to see if this was a feed request from the location bar or from
+  // the feed: protocol. This is also a reliable indication.
+  // The value of the header doesn't matter.
+  if (!noSniff) {
+    nsCAutoString sniffHeader;
+    nsresult foundHeader =
+      channel->GetRequestHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+                                sniffHeader);
+    noSniff = NS_SUCCEEDED(foundHeader);
+  }
+
+  if (noSniff) {
+    // check for an attachment after we have a likely feed.
+    if(HasAttachmentDisposition(channel)) {
+      sniffedType.Truncate();
+      return NS_OK;
+    }
+
+    // set the feed header as a response header, since we have good metadata
+    // telling us that the feed is supposed to be RSS or Atom
+    channel->SetResponseHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+                               NS_LITERAL_CSTRING("1"), PR_FALSE);
+    sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
+    return NS_OK;
+  }
+
+  // Don't sniff arbitrary types.  Limit sniffing to situations that
+  // we think can reasonably arise.
+  if (!contentType.EqualsLiteral(TEXT_HTML) &&
+      !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
+      // Same criterion as XMLHttpRequest.  Should we be checking for "+xml"
+      // and check for text/xml and application/xml by hand instead?
+      contentType.Find("xml") == -1) {
+    sniffedType.Truncate();
+    return NS_OK;
+  }
+
+  // Now we need to potentially decompress data served with
+  // Content-Encoding: gzip
+  nsresult rv = ConvertEncodedData(request, data, length);
+  if (NS_FAILED(rv))
+    return rv;
+
+  const char* testData =
+    mDecodedData.IsEmpty() ? (const char*)data : mDecodedData.get();
+
+  // The strategy here is based on that described in:
+  // http://blogs.msdn.com/rssteam/articles/PublishersGuide.aspx
+  // for interoperarbility purposes.
+
+  // We cap the number of bytes to scan at MAX_BYTES to prevent picking up
+  // false positives by accidentally reading document content, e.g. a "how to
+  // make a feed" page.
+  if (length > MAX_BYTES)
+    length = MAX_BYTES;
+
+  // Thus begins the actual sniffing.
+  nsDependentCSubstring dataString((const char*)testData, length);
+
+  PRBool isFeed = PR_FALSE;
+
+  // RSS 0.91/0.92/2.0
+  isFeed = ContainsTopLevelSubstring(dataString, "<rss");
+
+  // Atom 1.0
+  if (!isFeed)
+    isFeed = ContainsTopLevelSubstring(dataString, "<feed");
+
+  // RSS 1.0
+  if (!isFeed) {
+    isFeed = ContainsTopLevelSubstring(dataString, "<rdf:RDF") &&
+      dataString.Find(NS_RDF) != -1 &&
+      dataString.Find(NS_RSS) != -1;
+  }
+
+  // If we sniffed a feed, coerce our internal type
+  if (isFeed && !HasAttachmentDisposition(channel))
+    sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
+  else
+    sniffedType.Truncate();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+  return NS_OK;
+}
+
+NS_METHOD
+nsFeedSniffer::AppendSegmentToString(nsIInputStream* inputStream,
+                                     void* closure,
+                                     const char* rawSegment,
+                                     PRUint32 toOffset,
+                                     PRUint32 count,
+                                     PRUint32* writeCount)
+{
+  nsCString* decodedData = static_cast<nsCString*>(closure);
+  decodedData->Append(rawSegment, count);
+  *writeCount = count;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnDataAvailable(nsIRequest* request, nsISupports* context,
+                               nsIInputStream* stream, PRUint32 offset,
+                               PRUint32 count)
+{
+  PRUint32 read;
+  return stream->ReadSegments(AppendSegmentToString, &mDecodedData, count,
+                              &read);
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnStopRequest(nsIRequest* request, nsISupports* context,
+                             nsresult status)
+{
+  return NS_OK;
+}
+
+NS_METHOD
+nsFeedSniffer::Register(nsIComponentManager *compMgr, nsIFile *path,
+                        const char *registryLocation,
+                        const char *componentType,
+                        const nsModuleComponentInfo *info)
+{
+  nsresult rv;
+  nsCOMPtr<nsICategoryManager> catman = do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+  if (NS_FAILED(rv))
+    return rv;
+
+  return catman->AddCategoryEntry(NS_CONTENT_SNIFFER_CATEGORY, "Feed Sniffer",
+                                  NS_FEEDSNIFFER_CONTRACTID, PR_TRUE, PR_TRUE,
+                                  nsnull);
+}
new file mode 100644
--- /dev/null
+++ b/suite/feeds/src/nsFeedSniffer.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Feed Content Sniffer.
+ *
+ * The Initial Developer of the Original Code is Google Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Ben Goodger <beng@google.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+#include "nsIGenericFactory.h"
+#include "nsIContentSniffer.h"
+#include "nsIStreamListener.h"
+#include "nsStringAPI.h"
+
+#define NS_FEEDSNIFFER_CONTRACTID \
+  "@mozilla.org/browser/feeds/sniffer;1"
+
+// {E5EEEF51-05CE-4885-9434-7287616D9547}
+#define NS_FEEDSNIFFER_CID \
+  { 0xe5eeef51, 0x5ce, 0x4885, { 0x94, 0x34, 0x72, 0x87, 0x61, 0x6d, 0x95, 0x47 } }
+
+
+class nsFeedSniffer : public nsIContentSniffer, nsIStreamListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTSNIFFER
+  NS_DECL_NSIREQUESTOBSERVER
+  NS_DECL_NSISTREAMLISTENER
+
+  static NS_METHOD AppendSegmentToString(nsIInputStream* inputStream,
+                                         void* closure,
+                                         const char* rawSegment,
+                                         PRUint32 toOffset,
+                                         PRUint32 count,
+                                         PRUint32* writeCount);
+
+  static NS_METHOD Register(nsIComponentManager* compMgr, nsIFile* path,
+                            const char* registryLocation,
+                            const char* componentType,
+                            const nsModuleComponentInfo *info);
+
+protected:
+  nsresult ConvertEncodedData(nsIRequest* request, const PRUint8* data,
+                              PRUint32 length);
+
+private:
+  nsCString mDecodedData;
+};
--- a/suite/installer/unix/packages
+++ b/suite/installer/unix/packages
@@ -195,16 +195,17 @@ bin/components/sessionstore.xpt
 bin/components/shistory.xpt
 bin/components/shellservice.xpt
 bin/components/spellchecker.xpt
 bin/components/storage.xpt
 bin/components/libstoragecomps.so
 bin/components/libsuite.so
 bin/components/suitebrowser.xpt
 bin/components/suitecommon.xpt
+bin/components/suitefeeds.xpt
 bin/components/suitemigration.xpt
 bin/components/libsystem-pref.so
 bin/components/libtkautocomplete.so
 bin/components/libtoolkitcomps.so
 bin/components/toolkitprofile.xpt
 bin/components/toolkitremote.xpt
 bin/components/libtxmgr.so
 bin/components/txmgr.xpt
@@ -240,20 +241,24 @@ bin/components/libxpconnect.so
 bin/components/xpinstall.xpt
 bin/components/xulapp.xpt
 bin/components/xuldoc.xpt
 bin/components/xultmpl.xpt
 bin/components/zipwriter.xpt
 bin/components/libzipwriter.so
 
 ; JavaScript components
+bin/components/FeedConverter.js
 bin/components/FeedProcessor.js
+bin/components/FeedWriter.js
+bin/components/WebContentConverter.js
 bin/components/jsconsole-clhandler.js
 bin/components/nsAboutAbout.js
 bin/components/nsAboutCertError.js
+bin/components/nsAboutFeeds.js
 bin/components/nsAboutSessionRestore.js
 bin/components/nsAddonRepository.js
 bin/components/nsBadCertHandler.js
 bin/components/nsBlocklistService.js
 bin/components/nsBrowserContentHandler.js
 bin/components/nsComposerCmdLineHandler.js
 bin/components/nsContentDispatchChooser.js
 bin/components/nsContentPrefService.js
--- a/suite/installer/windows/packages
+++ b/suite/installer/windows/packages
@@ -193,16 +193,17 @@ bin\components\saxparser.xpt
 bin\components\sessionstore.xpt
 bin\components\shellservice.xpt
 bin\components\shistory.xpt
 bin\components\storage.xpt
 bin\components\strgcmps.dll
 bin\components\suite.dll
 bin\components\suitebrowser.xpt
 bin\components\suitecommon.xpt
+bin\components\suitefeeds.xpt
 bin\components\suitemigration.xpt
 bin\components\tkautoc.dll
 bin\components\tkitcmps.dll
 bin\components\toolkitprofile.xpt
 bin\components\txmgr.dll
 bin\components\txmgr.xpt
 bin\components\txtsvc.xpt
 bin\components\uconv.dll
@@ -241,20 +242,24 @@ bin\components\xultmpl.xpt
 bin\components\zipwriter.xpt
 bin\components\zipwriter.dll
 bin\plugins\npnul32.dll
 bin\components\helperAppDlg.xpt
 bin\AccessibleMarshal.dll
 bin\sqlite3.dll
 
 ; JavaScript components
+bin\components\FeedConverter.js
 bin\components\FeedProcessor.js
+bin\components\FeedWriter.js
+bin\components\WebContentConverter.js
 bin\components\jsconsole-clhandler.js
 bin\components\nsAboutAbout.js
 bin\components\nsAboutCertError.js
+bin\components\nsAboutFeeds.js
 bin\components\nsAboutSessionRestore.js
 bin\components\nsAddonRepository.js
 bin\components\nsAxSecurityPolicy.js
 bin\components\nsBadCertHandler.js
 bin\components\nsBlocklistService.js
 bin\components\nsBrowserContentHandler.js
 bin\components\nsComposerCmdLineHandler.js
 bin\components\nsContentDispatchChooser.js
new file mode 100644
--- /dev/null
+++ b/suite/locales/en-US/chrome/common/feeds/subscribe.dtd
@@ -0,0 +1,3 @@
+<!ENTITY feedPage.title "Viewing Feed">
+<!ENTITY feedSubscribeNow "Subscribe Now">
+<!ENTITY feedMessenger "News &amp; Blogs">
new file mode 100644
--- /dev/null
+++ b/suite/locales/en-US/chrome/common/feeds/subscribe.properties
@@ -0,0 +1,48 @@
+linkTitleTextFormat=Go to %S
+addHandler=Add "%S" (%S) as a Feed Reader?
+addHandlerAddButton=Add Feed Reader
+addHandlerAddButtonAccesskey=A
+handlerRegistered="%S" is already registered as a Feed Reader
+subscribeNow=Subscribe Now
+chooseApplicationMenuItem=Choose Application…
+chooseApplicationDialogTitle=Choose Application
+alwaysUse=Always use %S to subscribe to feeds
+mediaLabel=Media files
+
+# LOCALIZATION NOTE: The next string is for the size of the enclosed media.
+#   e.g. enclosureSizeText : "50.23 MB"
+#   %1$S = size (in bytes or megabytes, ...)
+#   %2$S = unit of measure (bytes, KB, MB, ...)
+enclosureSizeText=%1$S %2$S
+
+bytes=bytes
+kilobytes=KB
+megabytes=MB
+gigabytes=GB
+
+# LOCALIZATION NOTE: The next three strings explains to the user what they're
+# doing.
+#   e.g. alwaysUseForVideoPodcasts : "Always use Miro to subscribe to video podcasts."
+#   %S = application to use (Miro, iTunes, ...)
+alwaysUseForFeeds=Always use %S to subscribe to feeds.
+alwaysUseForAudioPodcasts=Always use %S to subscribe to podcasts.
+alwaysUseForVideoPodcasts=Always use %S to subscribe to video podcasts.
+
+subscribeFeedUsing=Subscribe to this feed using
+subscribeAudioPodcastUsing=Subscribe to this podcast using
+subscribeVideoPodcastUsing=Subscribe to this video podcast using
+
+feedSubscriptionFeed1=This is a "feed" of frequently changing content on this site.
+feedSubscriptionAudioPodcast1=This is a "podcast" of frequently changing content on this site.
+feedSubscriptionVideoPodcast1=This is a "video podcast" of frequently changing content on this site.
+
+feedSubscriptionFeed2=You can subscribe to this feed to receive updates when this content changes.
+feedSubscriptionAudioPodcast2=You can subscribe to this podcast to receive updates when this content changes.
+feedSubscriptionVideoPodcast2=You can subscribe to this video podcast to receive updates when this content changes.
+
+# Protocol Handling
+# "Add %appName (%appDomain) as an application for %protocolType links?"
+addProtocolHandler=Add %S (%S) as an application for %S links?
+addProtocolHandlerAddButton=Add Application
+# "%appName has already been added as an application for %protocolType links."
+protocolHandlerRegistered=%S has already been added as an application for %S links.
--- a/suite/locales/en-US/chrome/common/pref/pref-applications.properties
+++ b/suite/locales/en-US/chrome/common/pref/pref-applications.properties
@@ -15,16 +15,16 @@ videoPodcastFeed=Video Podcast
 audioPodcastFeed=Podcast
 alwaysAsk=Always ask
 
 # LOCALIZATION NOTE (usePluginIn):
 # %1$S = plugin name (for example "QuickTime Plugin-in 7.2")
 # %2$S = brandShortName from brand.properties (for example "Minefield")
 usePluginIn=Use %S (in %S)
 
-# LOCALIZATION NOTE (previewInApp, addLiveBookmarksInApp): %S = brandShortName
+# LOCALIZATION NOTE (previewInApp, addNewsBlogsInApp): %S = brandShortName
 previewInApp=Preview in %S
-addLiveBookmarksInApp=Add Live Bookmarks in %S
+addNewsBlogsInApp=Subscribe in %S
 
 # LOCALIZATION NOTE (typeDescriptionWithType):
 # %1$S = type description (for example "Portable Document Format")
 # %2$S = type (for example "application/pdf")
 typeDescriptionWithType=%S (%S)
--- a/suite/locales/jar.mn
+++ b/suite/locales/jar.mn
@@ -19,16 +19,18 @@
   locale/@AB_CD@/branding/brand.dtd                                         (%chrome/branding/brand.dtd)
   locale/@AB_CD@/branding/brand.properties                                  (%chrome/branding/brand.properties)
   locale/@AB_CD@/communicator/askViewZoom.dtd                               (%chrome/common/askViewZoom.dtd)
   locale/@AB_CD@/communicator/certError.dtd                                 (%chrome/common/certError.dtd)
   locale/@AB_CD@/communicator/consoleOverlay.dtd                            (%chrome/common/consoleOverlay.dtd)
   locale/@AB_CD@/communicator/contentAreaCommands.dtd                       (%chrome/common/contentAreaCommands.dtd)
   locale/@AB_CD@/communicator/contentAreaCommands.properties                (%chrome/common/contentAreaCommands.properties)
   locale/@AB_CD@/communicator/defaultClientDialog.dtd                       (%chrome/common/defaultClientDialog.dtd)
+  locale/@AB_CD@/communicator/feeds/subscribe.dtd                           (%chrome/common/feeds/subscribe.dtd)
+  locale/@AB_CD@/communicator/feeds/subscribe.properties                    (%chrome/common/feeds/subscribe.properties)
   locale/@AB_CD@/communicator/notification.properties                       (%chrome/common/notification.properties)
   locale/@AB_CD@/communicator/openLocation.dtd                              (%chrome/common/openLocation.dtd)
   locale/@AB_CD@/communicator/openLocation.properties                       (%chrome/common/openLocation.properties)
   locale/@AB_CD@/communicator/passwordManager.dtd                           (%chrome/common/passwordManager.dtd)
   locale/@AB_CD@/communicator/printPreview.dtd                              (%chrome/common/printPreview.dtd)
   locale/@AB_CD@/communicator/shellservice.properties                       (%chrome/common/shellservice.properties)
   locale/@AB_CD@/communicator/sanitize.dtd                                  (%chrome/common/sanitize.dtd)
   locale/@AB_CD@/communicator/tasksOverlay.dtd                              (%chrome/common/tasksOverlay.dtd)
--- a/suite/makefiles.sh
+++ b/suite/makefiles.sh
@@ -45,16 +45,18 @@ add_makefiles "
   suite/browser/src/Makefile
   suite/build/Makefile
   suite/debugQA/Makefile
   suite/debugQA/locales/Makefile
   suite/common/Makefile
   suite/common/public/Makefile
   suite/common/src/Makefile
   suite/common/tests/Makefile
+  suite/feeds/public/Makefile
+  suite/feeds/src/Makefile
   suite/installer/Makefile
   suite/installer/windows/Makefile
   suite/locales/Makefile
   suite/mailnews/Makefile
   suite/modules/Makefile
   suite/modules/test/Makefile
   suite/profile/Makefile
   suite/profile/migration/public/Makefile
new file mode 100644
--- /dev/null
+++ b/suite/themes/classic/communicator/feed-subscribe.css
@@ -0,0 +1,154 @@
+html {
+  background: -moz-Dialog;
+  font: message-box;
+}
+
+#feedBody {
+  border: 1px solid ThreeDShadow;
+  padding: 3em;
+  -moz-padding-start: 30px;
+  margin: 2em auto;
+  background: -moz-Field;
+}
+
+#feedHeaderContainer {
+  border: 1px solid ThreeDShadow;
+  -moz-border-radius: 10px;
+  margin: -4em auto 0 auto;
+  background-color: InfoBackground;
+}
+
+#feedHeader {
+  margin-top: 4.9em;
+  margin-bottom: 1em;
+  -moz-margin-start: 1.4em;
+  -moz-margin-end: 1em;
+  -moz-padding-start: 2.9em;
+  font-size: 110%;
+  color: InfoText;
+}
+
+.feedBackground {
+  background: url("chrome://communicator/skin/icons/feedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.videoPodcastBackground {
+  background: url("chrome://communicator/skin/icons/videoFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.audioPodcastBackground {
+  background: url("chrome://communicator/skin/icons/audioFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+#feedHeader[dir="rtl"] {
+  background-position: 100% 10%;
+}
+
+#feedIntroText {
+  display: none;
+}
+
+
+
+#feedHeader[firstrun="true"] #feedIntroText {
+  padding-top: 0.1em;
+  -moz-padding-start: 0.6em;
+  display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+  -moz-padding-start: 1.8em;
+}
+
+#feedSubscribeLine {
+  padding-top: 0.2em;
+}
+
+#messengerFeedsMenuItem {
+  list-style-image: url("chrome://communicator/skin/icons/feedIcon16.png");
+}
+
+#feedHeader[dir="rtl"] #handlersMenuList > menupopup {
+  direction: rtl;
+}
+
+/* Don't print subscription UI */
+@media print {
+  #feedHeaderContainer {
+    display: none;
+  }
+}
+
+body {
+  margin: 0;
+  padding: 0 3em;
+  color: -moz-fieldText;
+  font: message-box;
+}
+
+h1 {
+  font-size: 160%;
+  border-bottom: 2px solid ThreeDLightShadow;
+  margin: 0 0 .2em 0;
+}
+
+h2 {
+  font-size: 110%;
+  font-weight: normal;
+  margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+  float: right;
+  -moz-margin-start: .6em;
+  -moz-margin-end: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+a[href] img {
+  border: none;
+}
+
+#feedTitleContainer {
+  -moz-margin-start: 0;
+  -moz-margin-end: .6em;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+#feedTitleImage {
+  -moz-margin-start: .6em;
+  -moz-margin-end: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+  max-width: 300px;
+  max-height: 150px;
+}
+
+.feedEntryContent {
+  font-size: 110%;
+}
+
+.lastUpdated {
+  font-size: 85%;
+  font-weight: normal;
+}
+
+.type-icon {
+  vertical-align: bottom;
+  height: 16px;
+  width: 16px;
+}
+
+.enclosures {
+  border: 1px solid ThreeDShadow;
+  padding: 1em;
+  margin: 1em auto;
+  background: -moz-Dialog;
+}
+
+.enclosure {
+  vertical-align: middle;
+  margin-left: 2px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a788fffb00e59e3fff6931ad34cbc12a59f34254
GIT binary patch
literal 1794
zc$@(O2mSboP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H127gIJK~z|U#g}c2R!0@be>3-GpMCb(7YfV5E-YIjPz2Yui=`k<
zL}LQfA~vbo7Hn)%n>0-@0sY*dA52=m_|ih8Q7N`kC2E7n2Scjssx7Qbs1~Vo!~62m
z1-dTFv(KHG<A=F>@AI%rOiYNAoZOi+_nz~g|2b!7t`ZUcpDmo|3~W+sgD}`?jr|m$
zZ<9X>0{ynN?ge1)J+Fv_sq-~)^Yp##tJc;xb}mHe@K)b@k*cwPh)ai(ic9Alxb!K4
zIHRtg8(cng_z%0vA6{($eyT)-qhAgCI@fJn+1L^zSKk9?K&jm4ghvqssd7~1Fzt&t
zaIRMGoYyOby5la&<$#k1e{;P1C-yE4V2xeT*tHlL83e5$j3JD?L{ZIH1rriN5JC|6
zrw%{|C>?m6)?TMHXpPd^lXB%a^|QMWk*<MFs&#^ErGg02pi~4XQ26Hr<rAyRR;Ex+
zKvZoAT>6#9GockYojOUGK=}a@<iS?TqfZf|6sl`Idcl{x?eZ|(7>1FPxbuC;2S-6H
zpin4eB4kQ=ouoQZpwdMs{5Ve{s<JJOCq8EO!fB#a>mY9ScH?Gfnh$NCAXxASj9<f@
z>qU<3Mn=x&3{)rSloc+3%SL&*IYF#NtU+voJwHg&`#jiWDl1Uef}Z~n!P?#EdpASW
zRFjmFRYEROow$=2a5i=3;~&hNgZaz%F>k$uDz%|XvrsK_2^Op&_~-^yX=b{F2)*cw
z=sVY8`<}y{Iyku!CaPe<xtoFkaTYNNVk$_Ipr*AFEm=;qpqpUEG;}b64E2%hdzKsD
zX=i-rr?G=?=8VK8g4NHXKlUV8>lc)YxD=4H`EzmzESMyhBmrXpg^FTSv5}y4F5&#8
z1nu*Y3x^orxtj4;K93BaNR23ho@WUk*ap_7A*}}E^8O?Sj6sr0&O(hOx!xL7T!)_3
zMbOrXJ#m2Y50_!zJCGY)^ab=iPlGj{jr1wjAkL=MBRA)O@hgXH7YvqeM0Y<4$tZI9
z@5tqU;4U3SE*?d25Esxh=OV*HjPLp!@%k4D@B5lJy!`9P<zv_r2Woao#CcGYlF<59
zW{h0_kZ8#!RB4vSH0>j>a03D0hWatT--=v2g+frxt+;;=Q+eYVR7)qp!Zm;*`s{ac
zr~8nx;o1suCM(fA2TTH{lI4IKxr+Jo_dGK<H9fnB_|g67rH}j2*#uSJgbG6{KmT%`
z6LpQA32SNuo7>EDz?z!)t3r{a_XWnbwNT!%ipsvtm?J;S0fqHMtG`9`;C7#^wgQC`
zQW>SPe@o^dxcdoIsUuJBTLVatB*_v^MKQYR4s;wM7mr{M@1XMfM#?YUkGu44?qK1A
z1b08-!<QzgJ@!UkqCrf!Vsp-bNm)o^kcYq;AA-b-2V)Q?{%i<iRH29r4N(3;4`yIj
z_Gz^GIaE_8n8fdPT4N4w%Rw%B$S0mj{AM>517smcrYOZ!=NuA7U=u38dOBw!DiW{X
zj@TrtBzy9Y$mk^iR6{Fz&hmUOkg3jsED7^OL?G;boWfIoqVQBNVfSO{IU-=nRDSW5
zEGKm5O7!e|{p!*U$G-dPoQ1{fv;LF-B-^R--74&Ul=$JD=#CzA$1>uFUnczcW0?VW
zt{-!BPi}0^eN;F5ud2<dZAle?{4lDzd`><)!~384uR3@3?c7*LPtBZMJCpTQV|(??
z6afCm>$yAEUj<-UEr8JrIe?}r0M!gA<@a43$LL~1CTNZM>x+{d+P}S=zgH;WhK^)+
z20d+FK388vMlV8g!vm-*Wpk>o4uEls3V{fKD28GwN2~+Pk=;N}yBYW~?)`oFdm?b<
z5|#a1h(5U)Gw`FTIiP%r%J05G^uU(dxw--foEjkvfhMnv35pH*QtOMjx>4*quVUYM
z72Vi`E|v%iF%aiKoy8p5j{NgAbYoME4JFethj$>y_tea(x)=e~f04!zm=2(V5Ggj+
zp4IgY=;>{f!cg6Gp@?psee1ayvuc$}LlonL@YofaHBh{KgTeCU)2QY<{ayZcHKkFd
z4w5TFTu->NZ;)~bjQw_y*E{-px6WI!DlD|GM4|m35HR^y?B*+P?Ip8zUaEB7;`j&W
zIdiDrym^Mbz?cGN0CT>%j18Ze$0M!vEGGRexgG85IPdqK<(J=ni#^71M&UE62$X;V
k5Zp!p0$897i~?i-0r;%NI>fK;H~;_u07*qoM6N<$g0$pZj{pDw
--- a/suite/themes/classic/jar.mn
+++ b/suite/themes/classic/jar.mn
@@ -39,16 +39,17 @@ classic.jar:
   skin/classic/communicator/bookmarks/location-hov.gif                  (communicator/bookmarks/location-hov.gif)
   skin/classic/communicator/bookmarks/location.gif                      (communicator/bookmarks/location.gif)
   skin/classic/communicator/bookmarks/bookmarksToolbar.css              (communicator/bookmarks/bookmarksToolbar.css)
   skin/classic/communicator/brand/throbber-anim.png                     (communicator/brand/throbber-anim.png)
   skin/classic/communicator/brand/throbber-single.png                   (communicator/brand/throbber-single.png)
   skin/classic/communicator/brand/throbber16-anim.png                   (communicator/brand/throbber16-anim.png)
   skin/classic/communicator/brand/throbber16-single.png                 (communicator/brand/throbber16-single.png)
   skin/classic/communicator/directory/directory.css                     (communicator/directory/directory.css)
+  skin/classic/communicator/feed-subscribe.css                          (communicator/feed-subscribe.css)
   skin/classic/communicator/directory/folder-clsd.gif                   (communicator/directory/folder-clsd.gif)
   skin/classic/communicator/directory/folder-open.gif                   (communicator/directory/folder-open.gif)
   skin/classic/communicator/directory/file.gif                          (communicator/directory/file.gif)
   skin/classic/communicator/history/calendar.png                        (communicator/history/calendar.png)
   skin/classic/communicator/profile/migrate.gif                         (communicator/profile/migrate.gif)
   skin/classic/communicator/profile/profile.css                         (communicator/profile/profile.css)
   skin/classic/communicator/profile/profileManager.css                  (communicator/profile/profileManager.css)
   skin/classic/communicator/profile/profileicon-large.gif               (communicator/profile/profileicon-large.gif)
@@ -60,28 +61,30 @@ classic.jar:
   skin/classic/communicator/sidebar/sbtab-twisty.gif                    (communicator/sidebar/sbtab-twisty.gif)
   skin/classic/communicator/sidebar/sbtab-twisty-open.gif               (communicator/sidebar/sbtab-twisty-open.gif)
   skin/classic/communicator/sidebar/customize.css                       (communicator/sidebar/customize.css)
   skin/classic/communicator/sidebar/preview.css                         (communicator/sidebar/preview.css)
   skin/classic/communicator/sidebar/sidebarBindings.xml                 (communicator/sidebar/sidebarBindings.xml)
   skin/classic/communicator/sidebar/sidebar.css                         (communicator/sidebar/sidebar.css)
   skin/classic/communicator/sidebar/sidebarListView.css                 (communicator/sidebar/sidebarListView.css)
   skin/classic/communicator/xpinstall/xpinstall.css                     (communicator/xpinstall/xpinstall.css)
+  skin/classic/communicator/icons/audioFeedIcon.png                     (communicator/icons/feedIcon.png)
   skin/classic/communicator/icons/close-button.gif                      (communicator/icons/close-button.gif)
   skin/classic/communicator/icons/offline.png                           (communicator/icons/offline.png)
   skin/classic/communicator/icons/online.png                            (communicator/icons/online.png)
   skin/classic/communicator/icons/search.png                            (communicator/icons/search.png)
   skin/classic/communicator/icons/lock-secure.png                       (communicator/icons/lock-secure.png)
   skin/classic/communicator/icons/lock-broken.png                       (communicator/icons/lock-broken.png)
   skin/classic/communicator/icons/lock-insecure.png                     (communicator/icons/lock-insecure.png)
   skin/classic/communicator/icons/loading.gif                           (communicator/icons/loading.gif)
   skin/classic/communicator/icons/communicatoricons.png                 (communicator/icons/communicatoricons.png)
   skin/classic/communicator/icons/communicatoricons-small.png           (communicator/icons/communicatoricons-small.png)
   skin/classic/communicator/icons/alwaysAsk.png                         (communicator/icons/alwaysAsk.png)
   skin/classic/communicator/icons/application.png                       (communicator/icons/application.png)
+  skin/classic/communicator/icons/feedIcon.png                          (communicator/icons/feedIcon.png)
   skin/classic/communicator/icons/feedIcon16.png                        (communicator/icons/feedIcon16.png)
   skin/classic/communicator/icons/plugin.png                            (communicator/icons/plugin.png)
   skin/classic/communicator/icons/save.png                              (communicator/icons/save.png)
   skin/classic/communicator/icons/smileys/smiley-smile.png              (communicator/icons/smileys/smiley-smile.png)
   skin/classic/communicator/icons/smileys/smiley-frown.png              (communicator/icons/smileys/smiley-frown.png)
   skin/classic/communicator/icons/smileys/smiley-wink.png               (communicator/icons/smileys/smiley-wink.png)
   skin/classic/communicator/icons/smileys/smiley-tongue.png             (communicator/icons/smileys/smiley-tongue.png)
   skin/classic/communicator/icons/smileys/smiley-laughing.png           (communicator/icons/smileys/smiley-laughing.png)
@@ -91,16 +94,17 @@ classic.jar:
   skin/classic/communicator/icons/smileys/smiley-kiss.png               (communicator/icons/smileys/smiley-kiss.png)
   skin/classic/communicator/icons/smileys/smiley-yell.png               (communicator/icons/smileys/smiley-yell.png)
   skin/classic/communicator/icons/smileys/smiley-cool.png               (communicator/icons/smileys/smiley-cool.png)
   skin/classic/communicator/icons/smileys/smiley-money.png              (communicator/icons/smileys/smiley-money.png)
   skin/classic/communicator/icons/smileys/smiley-foot.png               (communicator/icons/smileys/smiley-foot.png)
   skin/classic/communicator/icons/smileys/smiley-innocent.png           (communicator/icons/smileys/smiley-innocent.png)
   skin/classic/communicator/icons/smileys/smiley-cry.png                (communicator/icons/smileys/smiley-cry.png)
   skin/classic/communicator/icons/smileys/smiley-sealed.png             (communicator/icons/smileys/smiley-sealed.png)
+  skin/classic/communicator/icons/videoFeedIcon.png                     (communicator/icons/feedIcon.png)
   skin/classic/communicator/taskbar/composer-hov.gif                    (communicator/taskbar/composer-hov.gif)
   skin/classic/communicator/taskbar/addressbook.gif                     (communicator/taskbar/addressbook.gif)
   skin/classic/communicator/taskbar/addressbook-16.gif                  (communicator/taskbar/addressbook-16.gif)
   skin/classic/communicator/taskbar/mailnew-hov.gif                     (communicator/taskbar/mailnew-hov.gif)
   skin/classic/communicator/taskbar/navigator.gif                       (communicator/taskbar/navigator.gif)
   skin/classic/communicator/taskbar/navigator-hov.gif                   (communicator/taskbar/navigator-hov.gif)
   skin/classic/communicator/taskbar/navigator-16.gif                    (communicator/taskbar/navigator-16.gif)
   skin/classic/communicator/taskbar/mail.gif                            (communicator/taskbar/mail.gif)
new file mode 100644
--- /dev/null
+++ b/suite/themes/modern/communicator/feed-subscribe.css
@@ -0,0 +1,191 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Organization.
+ * Portions created by the Initial Developer are Copyright (C) 1998-2002
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Caio Tiago Oliveira <asrail@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * 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 ***** */
+
+html {
+  background-color: #FFFFFF;
+  font: message-box;
+}
+
+#feedBody {
+  border: 1px solid #2D3B49;
+  padding: 3em;
+  -moz-padding-start: 30px;
+  margin: 2em auto;
+  background-color: #C7D0D9;
+  color: #22262F;
+}
+
+#feedHeaderContainer {
+  border: 1px solid #2D3B49;
+  -moz-border-radius: 10px;
+  margin: -4em auto 0 auto;
+  background-color: #C7D0D9;
+}
+
+#feedHeader {
+  margin-top: 4.9em;
+  margin-bottom: 1em;
+  -moz-margin-start: 1.4em;
+  -moz-margin-end: 1em;
+  -moz-padding-start: 2.9em;
+  font-size: 110%;
+  color: #000000;
+}
+
+.feedBackground {
+  background: url("chrome://communicator/skin/icons/feedIcon.png") 0% 10% no-repeat transparent;
+}
+
+.videoPodcastBackground {
+  background: url("chrome://communicator/skin/icons/videoFeedIcon.png") 0% 10% no-repeat transparent;
+}
+
+.audioPodcastBackground {
+  background: url("chrome://communicator/skin/icons/audioFeedIcon.png") 0% 10% no-repeat transparent;
+}
+
+#feedHeader[dir="rtl"] {
+  background-position: 100% 10%;
+}
+
+#feedIntroText {
+  display: none;
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+  padding-top: 0.1em;
+  -moz-padding-start: 0.6em;
+  display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+  -moz-padding-start: 1.8em;
+}
+
+#feedSubscribeLine {
+  padding-top: 0.2em;
+}
+
+#messengerFeedsMenuItem {
+  list-style-image: url("chrome://communicator/skin/icons/feedIcon16.png");
+}
+
+#feedHeader[dir="rtl"] #handlersMenuList > menupopup {
+  direction: rtl;
+}
+
+/* Don't print subscription UI */
+@media print {
+  #feedHeaderContainer {
+    display: none;
+  }
+}
+
+body {
+  margin: 0;
+  padding: 0 3em;
+  color: -moz-fieldText;
+  font: message-box;
+}
+
+h1 {
+  font-size: 160%;
+  border-bottom: 2px solid #7A8490;
+  margin: 0 0 .2em 0;
+}
+
+h2 {
+  font-size: 110%;
+  font-weight: normal;
+  margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+  float: right;
+  -moz-margin-start: .6em;
+  -moz-margin-end: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+a[href] img {
+  border: none;
+}
+
+#feedTitleContainer {
+  -moz-margin-start: 0;
+  -moz-margin-end: .6em;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+#feedTitleImage {
+  -moz-margin-start: .6em;
+  -moz-margin-end: 0;
+  margin-top: 0;
+  margin-bottom: 0;
+  max-width: 300px;
+  max-height: 150px;
+}
+
+.feedEntryContent {
+  font-size: 110%;
+}
+
+.lastUpdated {
+  font-size: 85%;
+  font-weight: normal;
+}
+
+.type-icon {
+  vertical-align: bottom;
+  height: 16px;
+  width: 16px;
+}
+
+.enclosures {
+  border: 1px solid #2D3B49;
+  padding: 1em;
+  margin: 1em auto;
+  background-color: #90A1B3;
+  color: #000000;
+}
+
+.enclosure {
+  vertical-align: middle;
+  margin-left: 2px;
+}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a788fffb00e59e3fff6931ad34cbc12a59f34254
GIT binary patch
literal 1794
zc$@(O2mSboP)<h;3K|Lk000e1NJLTq001BW001Be1^@s6b9#F800004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H127gIJK~z|U#g}c2R!0@be>3-GpMCb(7YfV5E-YIjPz2Yui=`k<
zL}LQfA~vbo7Hn)%n>0-@0sY*dA52=m_|ih8Q7N`kC2E7n2Scjssx7Qbs1~Vo!~62m
z1-dTFv(KHG<A=F>@AI%rOiYNAoZOi+_nz~g|2b!7t`ZUcpDmo|3~W+sgD}`?jr|m$
zZ<9X>0{ynN?ge1)J+Fv_sq-~)^Yp##tJc;xb}mHe@K)b@k*cwPh)ai(ic9Alxb!K4
zIHRtg8(cng_z%0vA6{($eyT)-qhAgCI@fJn+1L^zSKk9?K&jm4ghvqssd7~1Fzt&t
zaIRMGoYyOby5la&<$#k1e{;P1C-yE4V2xeT*tHlL83e5$j3JD?L{ZIH1rriN5JC|6
zrw%{|C>?m6)?TMHXpPd^lXB%a^|QMWk*<MFs&#^ErGg02pi~4XQ26Hr<rAyRR;Ex+
zKvZoAT>6#9GockYojOUGK=}a@<iS?TqfZf|6sl`Idcl{x?eZ|(7>1FPxbuC;2S-6H
zpin4eB4kQ=ouoQZpwdMs{5Ve{s<JJOCq8EO!fB#a>mY9ScH?Gfnh$NCAXxASj9<f@
z>qU<3Mn=x&3{)rSloc+3%SL&*IYF#NtU+voJwHg&`#jiWDl1Uef}Z~n!P?#EdpASW
zRFjmFRYEROow$=2a5i=3;~&hNgZaz%F>k$uDz%|XvrsK_2^Op&_~-^yX=b{F2)*cw
z=sVY8`<}y{Iyku!CaPe<xtoFkaTYNNVk$_Ipr*AFEm=;qpqpUEG;}b64E2%hdzKsD
zX=i-rr?G=?=8VK8g4NHXKlUV8>lc)YxD=4H`EzmzESMyhBmrXpg^FTSv5}y4F5&#8
z1nu*Y3x^orxtj4;K93BaNR23ho@WUk*ap_7A*}}E^8O?Sj6sr0&O(hOx!xL7T!)_3
zMbOrXJ#m2Y50_!zJCGY)^ab=iPlGj{jr1wjAkL=MBRA)O@hgXH7YvqeM0Y<4$tZI9
z@5tqU;4U3SE*?d25Esxh=OV*HjPLp!@%k4D@B5lJy!`9P<zv_r2Woao#CcGYlF<59
zW{h0_kZ8#!RB4vSH0>j>a03D0hWatT--=v2g+frxt+;;=Q+eYVR7)qp!Zm;*`s{ac
zr~8nx;o1suCM(fA2TTH{lI4IKxr+Jo_dGK<H9fnB_|g67rH}j2*#uSJgbG6{KmT%`
z6LpQA32SNuo7>EDz?z!)t3r{a_XWnbwNT!%ipsvtm?J;S0fqHMtG`9`;C7#^wgQC`
zQW>SPe@o^dxcdoIsUuJBTLVatB*_v^MKQYR4s;wM7mr{M@1XMfM#?YUkGu44?qK1A
z1b08-!<QzgJ@!UkqCrf!Vsp-bNm)o^kcYq;AA-b-2V)Q?{%i<iRH29r4N(3;4`yIj
z_Gz^GIaE_8n8fdPT4N4w%Rw%B$S0mj{AM>517smcrYOZ!=NuA7U=u38dOBw!DiW{X
zj@TrtBzy9Y$mk^iR6{Fz&hmUOkg3jsED7^OL?G;boWfIoqVQBNVfSO{IU-=nRDSW5
zEGKm5O7!e|{p!*U$G-dPoQ1{fv;LF-B-^R--74&Ul=$JD=#CzA$1>uFUnczcW0?VW
zt{-!BPi}0^eN;F5ud2<dZAle?{4lDzd`><)!~384uR3@3?c7*LPtBZMJCpTQV|(??
z6afCm>$yAEUj<-UEr8JrIe?}r0M!gA<@a43$LL~1CTNZM>x+{d+P}S=zgH;WhK^)+
z20d+FK388vMlV8g!vm-*Wpk>o4uEls3V{fKD28GwN2~+Pk=;N}yBYW~?)`oFdm?b<
z5|#a1h(5U)Gw`FTIiP%r%J05G^uU(dxw--foEjkvfhMnv35pH*QtOMjx>4*quVUYM
z72Vi`E|v%iF%aiKoy8p5j{NgAbYoME4JFethj$>y_tea(x)=e~f04!zm=2(V5Ggj+
zp4IgY=;>{f!cg6Gp@?psee1ayvuc$}LlonL@YofaHBh{KgTeCU)2QY<{ayZcHKkFd
z4w5TFTu->NZ;)~bjQw_y*E{-px6WI!DlD|GM4|m35HR^y?B*+P?Ip8zUaEB7;`j&W
zIdiDrym^Mbz?cGN0CT>%j18Ze$0M!vEGGRexgG85IPdqK<(J=ni#^71M&UE62$X;V
k5Zp!p0$897i~?i-0r;%NI>fK;H~;_u07*qoM6N<$g0$pZj{pDw
--- a/suite/themes/modern/jar.mn
+++ b/suite/themes/modern/jar.mn
@@ -33,22 +33,25 @@ modern.jar:
   skin/modern/communicator/brand/throbber-anim.png                 (communicator/brand/throbber-anim.png)
   skin/modern/communicator/brand/throbber-single.png               (communicator/brand/throbber-single.png)
   skin/modern/communicator/brand/throbber16-anim.png               (communicator/brand/throbber16-anim.png)
   skin/modern/communicator/brand/throbber16-single.png             (communicator/brand/throbber16-single.png)
   skin/modern/communicator/directory/file-folder-closed.gif        (communicator/directory/file-folder-closed.gif)
   skin/modern/communicator/directory/file-folder-open.gif          (communicator/directory/file-folder-open.gif)
   skin/modern/communicator/directory/file-icon.gif                 (communicator/directory/file-icon.gif)
   skin/modern/communicator/directory/directory.css                 (communicator/directory/directory.css)
+  skin/modern/communicator/feed-subscribe.css                      (communicator/feed-subscribe.css)
   skin/modern/communicator/history/calendar.png                    (communicator/history/calendar.png)
   skin/modern/communicator/icons/alwaysAsk.png                     (communicator/icons/alwaysAsk.png)
   skin/modern/communicator/icons/application.png                   (communicator/icons/application.png)
+  skin/modern/communicator/icons/audioFeedIcon.png                 (communicator/icons/feedIcon.png)
   skin/modern/communicator/icons/btn1.gif                          (communicator/icons/btn1.gif)
   skin/modern/communicator/icons/common.png                        (communicator/icons/common.png)
   skin/modern/communicator/icons/common-small.png                  (communicator/icons/common-small.png)
+  skin/modern/communicator/icons/feedIcon.png                      (communicator/icons/feedIcon.png)
   skin/modern/communicator/icons/feedIcon16.png                    (communicator/icons/feedIcon16.png)
   skin/modern/communicator/icons/loading.gif                       (communicator/icons/loading.gif)
   skin/modern/communicator/icons/lock-broken.png                   (communicator/icons/lock-broken.png)
   skin/modern/communicator/icons/lock-insecure.png                 (communicator/icons/lock-insecure.png)
   skin/modern/communicator/icons/lock-secure.png                   (communicator/icons/lock-secure.png)
   skin/modern/communicator/icons/offline.gif                       (communicator/icons/offline.gif)
   skin/modern/communicator/icons/online.gif                        (communicator/icons/online.gif)
   skin/modern/communicator/icons/plugin.png                        (communicator/icons/plugin.png)
@@ -64,16 +67,17 @@ modern.jar:
   skin/modern/communicator/icons/smileys/smiley-kiss.png           (communicator/icons/smileys/smiley-kiss.png)
   skin/modern/communicator/icons/smileys/smiley-yell.png           (communicator/icons/smileys/smiley-yell.png)
   skin/modern/communicator/icons/smileys/smiley-cool.png           (communicator/icons/smileys/smiley-cool.png)
   skin/modern/communicator/icons/smileys/smiley-money.png          (communicator/icons/smileys/smiley-money.png)
   skin/modern/communicator/icons/smileys/smiley-foot.png           (communicator/icons/smileys/smiley-foot.png)
   skin/modern/communicator/icons/smileys/smiley-innocent.png       (communicator/icons/smileys/smiley-innocent.png)
   skin/modern/communicator/icons/smileys/smiley-cry.png            (communicator/icons/smileys/smiley-cry.png)
   skin/modern/communicator/icons/smileys/smiley-sealed.png         (communicator/icons/smileys/smiley-sealed.png)
+  skin/modern/communicator/icons/videoFeedIcon.png                 (communicator/icons/feedIcon.png)
   skin/modern/communicator/profile/migrate.gif                     (communicator/profile/migrate.gif)
   skin/modern/communicator/profile/profile.gif                     (communicator/profile/profile.gif)
   skin/modern/communicator/profile/profile.css                     (communicator/profile/profile.css)
   skin/modern/communicator/search/category.gif                     (communicator/search/category.gif)
   skin/modern/communicator/search/result.gif                       (communicator/search/result.gif)
   skin/modern/communicator/search/internetresults.css              (communicator/search/internetresults.css)
   skin/modern/communicator/search/search-editor.css                (communicator/search/search-editor.css)
   skin/modern/communicator/search/search.css                       (communicator/search/search.css)