--- 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 & 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 0000000000000000000000000000000000000000..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
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 0000000000000000000000000000000000000000..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
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)