gecko-dev/browser/components/feeds/src/FeedWriter.js

623 lines
20 KiB
JavaScript
Raw Normal View History

# -*- 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>
# Jeff Walden <jwalden+code@mit.edu>
#
# 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 Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
function LOG(str) {
var prefB =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
var shouldLog = false;
try {
shouldLog = prefB.getBoolPref("feeds.log");
}
catch (ex) {
}
if (shouldLog)
dump("*** Feeds: " + str + "\n");
}
const XML_NS = "http://www.w3.org/XML/1998/namespace"
const HTML_NS = "http://www.w3.org/1999/xhtml";
const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
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_SKIP_PREVIEW_PAGE = "browser.feeds.skip_preview_page";
const FW_CLASSID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
const FW_CLASSNAME = "Feed Writer";
const FW_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
function FeedWriter() {
}
FeedWriter.prototype = {
_getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
return container.fields.getProperty(property).
QueryInterface(Ci.nsIPropertyBag2);
},
_getPropertyAsString: function FW__getPropertyAsString(container, property) {
try {
return container.fields.getPropertyAsAString(property);
}
catch (e) {
}
return "";
},
_setContentText: function FW__setContentText(id, text) {
var element = this._document.getElementById(id);
while (element.hasChildNodes())
element.removeChild(element.firstChild);
element.appendChild(this._document.createTextNode(text));
},
/**
* 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 FW__safeSetURIAttribute(element, attribute, uri) {
var secman =
Cc["@mozilla.org/scriptsecuritymanager;1"].
getService(Ci.nsIScriptSecurityManager);
const flags = Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT_OR_DATA;
try {
secman.checkLoadURIStr(this._window.location.href, uri, flags);
// checkLoadURIStr will throw if the link URI should not be loaded per
// the rules specified in |flags|, so we'll never "linkify" the link...
element.setAttribute(attribute, uri);
}
catch (e) {
// Not allowed to load this link because secman.checkLoadURIStr threw
}
},
get _bundle() {
var sbs =
Cc["@mozilla.org/intl/stringbundle;1"].
getService(Ci.nsIStringBundleService);
return sbs.createBundle(URI_BUNDLE);
},
_getFormattedString: function FW__getFormattedString(key, params) {
return this._bundle.formatStringFromName(key, params, params.length);
},
_getString: function FW__getString(key) {
return this._bundle.GetStringFromName(key);
},
/**
* Writes the feed title into the preview document.
* @param container
* The feed container
*/
_setTitleText: function FW__setTitleText(container) {
LOG(container);
if(container.title) {
this._setContentText("feedTitleText", container.title.plainText());
this._document.title = container.title.plainText();
}
var feed = container.QueryInterface(Ci.nsIFeed);
if(feed && feed.subtitle)
this._setContentText("feedSubtitleText", container.subtitle.plainText());
},
/**
* Writes the title image into the preview document if one is present.
* @param container
* The feed container
*/
_setTitleImage: function FW__setTitleImage(container) {
try {
var parts = this._getPropertyAsBag(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")]);
feedTitleLink.setAttribute("title", titleText);
this._safeSetURIAttribute(feedTitleLink, "href",
parts.getPropertyAsAString("link"));
// Fix the margin on the main title, so that the image doesn't run over
// the underline
var feedTitleText = this._document.getElementById("feedTitleText");
var titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
feedTitleText.style.marginRight = titleImageWidth + "px";
}
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 FW__writeFeedContent(container) {
// Build the actual feed content
var feedContent = this._document.getElementById("feedContent");
var feed = container.QueryInterface(Ci.nsIFeed);
for (var i = 0; i < feed.items.length; ++i) {
var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
entry.QueryInterface(Ci.nsIFeedContainer);
var entryContainer = this._document.createElementNS(HTML_NS, "div");
entryContainer.className = "entry";
// If the entry has a title, make it a like
if (entry.title) {
var 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);
var title = this._document.createElementNS(HTML_NS, "h3");
title.appendChild(a);
entryContainer.appendChild(title);
}
var body = this._document.createElementNS(HTML_NS, "p");
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);
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);
feedContent.appendChild(entryContainer);
}
},
/**
* 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 FW__getContainer(result) {
var feedService =
Cc["@mozilla.org/browser/feeds/result-service;1"].
getService(Ci.nsIFeedResultService);
var ios =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var feedURI = ios.newURI(this._window.location.href, null, null);
try {
var result = feedService.getFeedResult(feedURI);
}
catch (e) {
LOG("Subscribe Preview: feed not available?!");
}
if (result.bozo) {
LOG("Subscribe Preview: feed result is bozo?!");
}
try {
var container = result.doc;
container.title;
}
catch (e) {
LOG("Subscribe Preview: An error occurred in parsing! Fortunately, you can still subscribe...");
var feedError = this._document.getElementById("feedError");
feedError.removeAttribute("style");
var feedBody = this._document.getElementById("feedBody");
feedBody.setAttribute("style", "display:none;");
this._setContentText("errorCode", e);
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 FW__getFileDisplayName(file) {
#ifdef XP_WIN
if (file instanceof Ci.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 ios =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
var url = ios.newFileURI(file).QueryInterface(Ci.nsIURL);
return url.fileName;
},
_initSelectedHandler: function FW__initSelectedHandler() {
var prefs =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
var chosen =
this._document.getElementById("feedSubscribeLineHandlerChosen");
var unchosen =
this._document.getElementById("feedSubscribeLineHandlerUnchosen");
var ios =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
try {
var iconURI = "chrome://browser/skin/places/livemarkItem.png";
var handler = prefs.getCharPref(PREF_SELECTED_ACTION);
if (handler == "reader")
handler = prefs.getCharPref(PREF_SELECTED_READER);
switch (handler) {
case "client":
var selectedApp =
prefs.getComplexValue(PREF_SELECTED_APP, Ci.nsILocalFile);
var displayName = this._getFileDisplayName(selectedApp);
this._setContentText("feedSubscribeHandleText", displayName);
var url = ios.newFileURI(selectedApp).QueryInterface(Ci.nsIURL);
iconURI = "moz-icon://" + url.spec;
break;
case "web":
var webURI = prefs.getCharPref(PREF_SELECTED_WEB);
var wccr =
Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
var title ="Unknown";
var handler =
wccr.getWebContentHandlerByURI(TYPE_MAYBE_FEED, webURI);
if (handler)
title = handler.name;
var uri = ios.newURI(webURI, null, null);
iconURI = uri.prePath + "/favicon.ico";
this._setContentText("feedSubscribeHandleText", title);
break;
case "bookmarks":
this._setContentText("feedSubscribeHandleText",
this._getString("liveBookmarks"));
break;
case "ask":
var alwaysAsk = true;
break;
}
if (!alwaysAsk) {
unchosen.setAttribute("hidden", "true");
chosen.removeAttribute("hidden");
var button = this._document.getElementById("feedSubscribeLink");
button.focus();
var displayArea =
this._document.getElementById("feedSubscribeHandleText");
displayArea.style.setProperty("background-image",
"url(\"" + iconURI + "\")", "");
return;
} else {
// user chose a feed reader but didn't make it the default -- fall
// through to "no selected handlers"
}
} catch (e) {
// prefs mismatch (e.g., handler==web but no web handler set),
// or (more likely) no prefs set yet
}
// No selected handlers, or user hasn't made his choice the default;
// make the user choose...
chosen.setAttribute("hidden", "true");
unchosen.removeAttribute("hidden");
this._document.getElementById("feedHeader").setAttribute("firstrun", "true");
var button = this._document.getElementById("feedChooseInitialReader");
button.focus();
},
/**
* Ensures that this component is only ever invoked from the preview
* document.
* @param window
* The window of the document invoking the BrowserFeedWriter
*/
_isValidWindow: function FW__isValidWindow(window) {
var chan =
window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
const kPrefix = "jar:file:";
return chan.URI.spec.substring(0, kPrefix.length) == kPrefix;
},
_window: null,
_document: null,
/**
* See nsIFeedWriter
*/
write: function FW_write(window) {
if (!this._isValidWindow(window))
return;
this._window = window;
this._document = window.document;
LOG("Subscribe Preview: feed uri = " + this._window.location.href);
// Set up the displayed handler
this._initSelectedHandler();
var prefs =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch2);
prefs.addObserver(PREF_SELECTED_ACTION, this, false);
prefs.addObserver(PREF_SELECTED_READER, this, false);
prefs.addObserver(PREF_SELECTED_APP, this, false);
// Set up the feed content
var container = this._getContainer();
if (!container)
return;
this._setTitleText(container);
this._setTitleImage(container);
this._writeFeedContent(container);
},
/**
* See nsIFeedWriter
*/
changeOptions: function FW_changeOptions() {
var paramBlock =
Cc["@mozilla.org/embedcomp/dialogparam;1"].
createInstance(Ci.nsIDialogParamBlock);
// Used to tell the preview page that the user chose to subscribe with
// a particular reader, and so it should subscribe now.
const PARAM_USER_SUBSCRIBED = 0;
paramBlock.SetInt(PARAM_USER_SUBSCRIBED, 0);
this._window.openDialog("chrome://browser/content/feeds/options.xul", "",
"modal,centerscreen", "subscribe", paramBlock);
if (paramBlock.GetInt(PARAM_USER_SUBSCRIBED) == 1)
this.subscribe();
},
/**
* See nsIFeedWriter
*/
close: function FW_close() {
this._document = null;
this._window = null;
var prefs =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch2);
prefs.removeObserver(PREF_SELECTED_ACTION, this);
prefs.removeObserver(PREF_SELECTED_READER, this);
prefs.removeObserver(PREF_SELECTED_APP, this);
},
/**
* See nsIFeedWriter
*/
subscribe: function FW_subscribe() {
var prefs =
Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
try {
var handler = prefs.getCharPref(PREF_SELECTED_ACTION);
if (handler == "reader" || handler == "ask")
// the actual handler will be stored in the reader preference
handler = prefs.getCharPref(PREF_SELECTED_READER);
}
catch (e) {
// Something's bogus in preferences -- make the user fix it
this.changeOptions();
return;
}
if (handler == "web") {
var webURI = prefs.getCharPref(PREF_SELECTED_WEB);
var wccr =
Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
getService(Ci.nsIWebContentConverterService);
var handler =
wccr.getWebContentHandlerByURI(TYPE_MAYBE_FEED, webURI);
this._window.location.href =
handler.getHandlerURI(this._window.location.href);
}
else {
// handles bookmarks, client cases
var request =
this._window.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
var feedService =
Cc["@mozilla.org/browser/feeds/result-service;1"].
getService(Ci.nsIFeedResultService);
feedService.addToClientReader(request, this._window.location.href);
}
},
/**
* See nsIObserver
*/
observe: function FW_observe(subject, topic, data) {
if (topic == "nsPref:changed")
this._initSelectedHandler();
},
/**
* See nsIClassInfo
*/
getInterfaces: function WCCR_getInterfaces(countRef) {
var interfaces =
[Ci.nsIFeedWriter, Ci.nsIClassInfo, Ci.nsISupports];
countRef.value = interfaces.length;
return interfaces;
},
getHelperForLanguage: function WCCR_getHelperForLanguage(language) {
return null;
},
contractID: FW_CONTRACTID,
classDescription: FW_CLASSNAME,
classID: FW_CLASSID,
implementationLanguage: Ci.nsIProgrammingLanguage.JAVASCRIPT,
flags: Ci.nsIClassInfo.DOM_OBJECT,
QueryInterface: function FW_QueryInterface(iid) {
if (iid.equals(Ci.nsIFeedWriter) ||
iid.equals(Ci.nsIClassInfo) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
var Module = {
QueryInterface: function M_QueryInterface(iid) {
if (iid.equals(Ci.nsIModule) ||
iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
},
getClassObject: function M_getClassObject(cm, cid, iid) {
if (!iid.equals(Ci.nsIFactory))
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
if (cid.equals(FW_CLASSID))
return new GenericComponentFactory(FeedWriter);
throw Cr.NS_ERROR_NO_INTERFACE;
},
registerSelf: function M_registerSelf(cm, file, location, type) {
var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
cr.registerFactoryLocation(FW_CLASSID, FW_CLASSNAME, FW_CONTRACTID,
file, location, type);
var catman =
Cc["@mozilla.org/categorymanager;1"].
getService(Ci.nsICategoryManager);
catman.addCategoryEntry("JavaScript global constructor",
"BrowserFeedWriter", FW_CONTRACTID, true, true);
},
unregisterSelf: function M_unregisterSelf(cm, location, type) {
var cr = cm.QueryInterface(Ci.nsIComponentRegistrar);
cr.unregisterFactoryLocation(FW_CLASSID, location);
},
canUnload: function M_canUnload(cm) {
return true;
}
};
function NSGetModule(cm, file) {
return Module;
}
#include ../../../../toolkit/content/debug.js
#include GenericFactory.js