Mercurial > snowl
changeset 46:b0bcbcfe8c52
the new river of news view, based on the subscribe page in Firefox; this doesn't work yet, it's just a copy of the files with some minimal massaging to fit them into the snowl directory/file structure (and make the FeedWriter XPCOM component into a RiverWriter JS module)
author | Myk Melez <myk@mozilla.org> |
---|---|
date | Thu, 01 May 2008 15:06:42 -0700 |
parents | a3811857c5dc |
children | 4036d4b914bc |
files | extension/content/river.js extension/content/river.xhtml extension/modules/RiverWriter.js |
diffstat | 3 files changed, 1500 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extension/content/river.js Thu May 01 15:06:42 2008 -0700 @@ -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 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 ***** */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://snowl/modules/RiverWriter.js"); + +var RiverHandler = { + /** + * The SnowlRiverWriter object that produces the UI. + */ + _riverWriter: null, + + init: function SH_init() { + this._riverWriter = new SnowlRiverWriter(); + this._riverWriter.init(window); + }, + + writeContent: function SH_writeContent() { + this._riverWriter.writeContent(); + }, + + uninit: function SH_uninit() { + this._riverWriter.close(); + }, + + subscribe: function FH_subscribe() { + this._riverWriter.subscribe(); + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extension/content/river.xhtml Thu May 01 15:06:42 2008 -0700 @@ -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://browser/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" + xmlns:aaa="http://www.w3.org/2005/07/aaa"> + <head> + <title>&feedPage.title;</title> + <link rel="stylesheet" + href="chrome://browser/skin/feeds/subscribe.css" + type="text/css" + media="all"/> + <script type="application/x-javascript" + src="chrome://snowl/content/river.js"/> + </head> + <body onload="RiverHandler.writeContent();" onunload="RiverHandler.uninit();"> + <div id="feedHeaderContainer"> + <div id="feedHeader" dir="&locale.dir;"> + <div id="feedIntroText" + ><xul:description id="feedSubscriptionInfo1" /><xul:description 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" aaa:labelledby="subscribeUsingDescription" + ><xul:menupopup menugenerated="true" id="handlersMenuPopup" + ><xul:menuitem id="liveBookmarksMenuItem" label="&feedLiveBookmarks;" class="menuitem-iconic" image="chrome://browser/skin/page-livemarks.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"> + RiverHandler.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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extension/modules/RiverWriter.js Thu May 01 15:06:42 2008 -0700 @@ -0,0 +1,1352 @@ +/* + * -*- 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> + * Asaf Romano <mano@mozilla.com> + * Robert Sayre <sayrer@gmail.com> + * Michael Ventnor <m.ventnor@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 ***** */ + +const EXPORTED_SYMBOLS = ["SnowlRiverWriter"]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +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"); +} + +/** + * 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) { + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + try { + return ios.newURI(aURLSpec, aCharset, null); + } catch (ex) { } + + return null; +} + +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 URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties"; +const SUBSCRIBE_PAGE_URI = "chrome://browser/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 Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_APP; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_APP; + + default: + return PREF_SELECTED_APP; + } +} + +function getPrefWebForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_WEB; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_WEB; + + default: + return PREF_SELECTED_WEB; + } +} + +function getPrefActionForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_ACTION; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_ACTION; + + default: + return PREF_SELECTED_ACTION; + } +} + +function getPrefReaderForType(t) { + switch (t) { + case Ci.nsIFeed.TYPE_VIDEO: + return PREF_VIDEO_SELECTED_READER; + + case Ci.nsIFeed.TYPE_AUDIO: + return PREF_AUDIO_SELECTED_READER; + + default: + return PREF_SELECTED_READER; + } +} + +/** + * 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", "kilobyte", "megabyte", "gigabyte"]; + let 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 SnowlRiverWriter() {} +SnowlRiverWriter.prototype = { + _mimeSvc : Cc["@mozilla.org/mime;1"]. + getService(Ci.nsIMIMEService), + + _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) { + 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);"; + Cu.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 FW__safeSetURIAttribute(element, attribute, uri) { + var secman = Cc["@mozilla.org/scriptsecuritymanager;1"]. + getService(Ci.nsIScriptSecurityManager); + const flags = Ci.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... + } + catch (e) { + // Not allowed to load this link because secman.checkLoadURIStr threw + return; + } + + this._contentSandbox.element = element; + this._contentSandbox.uri = uri; + var codeStr = "element.setAttribute('" + attribute + "', uri);"; + Cu.evalInSandbox(codeStr, this._contentSandbox); + }, + + /** + * 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 Cu.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 FW___safeDoCommand(aElement) { + this._contentSandbox.element = aElement; + Cu.evalInSandbox("element.doCommand();", this._contentSandbox); + this._contentSandbox.element = null; + }, + + __faviconService: null, + get _faviconService() { + if (!this.__faviconService) + this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"]. + getService(Ci.nsIFaviconService); + + return this.__faviconService; + }, + + __bundle: null, + get _bundle() { + if (!this.__bundle) { + this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"]. + getService(Ci.nsIStringBundleService). + createBundle(URI_BUNDLE); + } + return this.__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); + }, + + /* Magic helper methods to be used instead of xbl properties */ + _getSelectedItemFromMenulist: function FW__getSelectedItemFromList(aList) { + var node = aList.firstChild.firstChild; + while (node) { + if (node.localName == "menuitem" && node.getAttribute("selected") == "true") + return node; + + node = node.nextSibling; + } + + return null; + }, + + _setCheckboxCheckedState: function FW__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);" + } + + Cu.evalInSandbox(codeStr, this._contentSandbox); + }, + + /** + * Returns a date suitable for displaying in the feed preview. + * If the date cannot be parsed, the return value is "false". + * @param dateString + * A date as extracted from a feed entry. (entry.updated) + */ + _parseDate: function FW__parseDate(dateString) { + // Convert the date into the user's local time zone + dateObj = new Date(dateString); + + // Make sure the date we're given is valid. + if (!dateObj.getTime()) + return false; + + var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"]. + getService(Ci.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 FW__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(Ci.nsIFeed); + this.__feedType = feed.type; + return feed.type; + } catch (ex) { } + + return Ci.nsIFeed.TYPE_FEED; + }, + + /** + * Maps a feed type to a maybe-feed mimetype. + */ + _getMimeTypeForFeedType: function FW__getMimeTypeForFeedType() { + switch (this._getFeedType()) { + case Ci.nsIFeed.TYPE_VIDEO: + return TYPE_MAYBE_VIDEO_FEED; + + case Ci.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 FW__setTitleText(container) { + if (container.title) { + this._setContentText(TITLE_ID, container.title.plainText()); + this._document.title = container.title.plainText(); + } + + var feed = container.QueryInterface(Ci.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 FW__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; + var codeStr = "feedTitleLink.setAttribute('title', titleText);"; + Cu.evalInSandbox(codeStr, this._contentSandbox); + this._contentSandbox.feedTitleLink = null; + this._contentSandbox.titleText = null; + + 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 feed = container.QueryInterface(Ci.nsIFeed); + if (feed.items.length == 0) + return; + + this._contentSandbox.feedContent = + this._document.getElementById("feedContent"); + + 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 link + 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); + + var lastUpdated = this._parseDate(entry.updated); + if (lastUpdated) { + var 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);" + Cu.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 FW__getURLDisplayName(aURL) { + var url = makeURI(aURL); + url.QueryInterface(Ci.nsIURL); + if (url == null || url.fileName.length == 0) + return aURL; + + return decodeURI(url.fileName); + }, + + /** + * Takes a FeedEntry with enclosures, generates the HTML code to represent + * them, and returns that. + * @param entry + * FeedEntry with enclosures + * @returns element + */ + _buildEnclosureDiv: function FW__buildEnclosureDiv(entry) { + var enclosuresDiv = this._document.createElementNS(HTML_NS, "div"); + enclosuresDiv.className = "enclosures"; + + enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel"))); + + var roundme = function(n) { + return (Math.round(n * 100) / 100).toLocaleString(); + } + + for (var i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) { + var enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2); + + if (!(enc.hasKey("url"))) + continue; + + var enclosureDiv = this._document.createElementNS(HTML_NS, "div"); + enclosureDiv.setAttribute("class", "enclosure"); + + var mozicon = "moz-icon://.txt?size=16"; + var type_text = null; + var size_text = null; + + if (enc.hasKey("type")) { + type_text = enc.get("type"); + try { + var 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"))) { + var enc_size = convertByteUnits(parseInt(enc.get("length"))); + + var size_text = this._getFormattedString("enclosureSizeText", + [enc_size[0], this._getString(enc_size[1])]); + } + + var iconimg = this._document.createElementNS(HTML_NS, "img"); + iconimg.setAttribute("src", mozicon); + iconimg.setAttribute("class", "type-icon"); + enclosureDiv.appendChild(iconimg); + + enclosureDiv.appendChild(this._document.createTextNode( " " )); + + var 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 FW__getContainer(result) { + var feedService = + Cc["@mozilla.org/browser/feeds/result-service;1"]. + getService(Ci.nsIFeedResultService); + + try { + var result = + feedService.getFeedResult(this._getOriginalURI(this._window)); + } + catch (e) { + LOG("Subscribe Preview: feed not available?!"); + } + + if (result.bozo) { + LOG("Subscribe Preview: feed result is bozo?!"); + } + + try { + var container = result.doc; + } + catch (e) { + LOG("Subscribe Preview: no result.doc? Why didn't the original reload?"); + return null; + } + return container; + }, + + /** + * 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 FW__getFileIconURL(file) { + var ios = Cc["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); + var fph = ios.getProtocolHandler("file") + .QueryInterface(Ci.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);" + Cu.evalInSandbox(codeStr, this._contentSandbox); + }, + + _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) { + var checkbox = this._document.getElementById("alwaysUse"); + if (checkbox) { + var alwaysUse = false; + try { + var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask") + alwaysUse = true; + } + catch(ex) { } + this._setCheckboxCheckedState(checkbox, alwaysUse); + } + }, + + _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() { + var stringLabel = "subscribeFeedUsing"; + switch (this._getFeedType()) { + case Ci.nsIFeed.TYPE_VIDEO: + stringLabel = "subscribeVideoPodcastUsing"; + break; + + case Ci.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);" + Cu.evalInSandbox(codeStr, this._contentSandbox); + }, + + _setAlwaysUseLabel: function FW__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 Ci.nsIFeed.TYPE_VIDEO: + stringLabel = "alwaysUseForVideoPodcasts"; + break; + + case Ci.nsIFeed.TYPE_AUDIO: + stringLabel = "alwaysUseForAudioPodcasts"; + break; + } + + this._contentSandbox.checkbox = checkbox; + this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]); + + var codeStr = "checkbox.setAttribute('label', label);"; + Cu.evalInSandbox(codeStr, this._contentSandbox); + } + } + }, + + // nsIDomEventListener + handleEvent: function(event) { + // see comments in the write method + event = new XPCNativeWrapper(event); + if (event.target.ownerDocument != this._document) { + LOG("SnowlRiverWriter.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 FW__setSelectedHandler(feedType) { + var prefs = + Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + + var handler = "bookmarks"; + try { + handler = prefs.getCharPref(getPrefReaderForType(feedType)); + } + catch (ex) { } + + switch (handler) { + case "web": { + var handlersMenuList = this._document.getElementById("handlersMenuList"); + if (handlersMenuList) { + var url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data; + var handlers = + handlersMenuList.getElementsByAttribute("webhandlerurl", url); + if (handlers.length == 0) { + LOG("SnowlRiverWriter._setSelectedHandler: selected web handler isn't in the menulist") + return; + } + + this._safeDoCommand(handlers[0]); + } + break; + } + case "client": { + try { + this._selectedApp = + prefs.getComplexValue(getPrefAppForType(feedType), Ci.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 + ";" + } + Cu.evalInSandbox(codeStr, this._contentSandbox); + break; + } + } + case "bookmarks": + default: { + var liveBookmarksMenuItem = this._document.getElementById("liveBookmarksMenuItem"); + if (liveBookmarksMenuItem) + this._safeDoCommand(liveBookmarksMenuItem); + } + } + }, + + _initSubscriptionUI: function FW__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 Ci.nsIFeed.TYPE_VIDEO: + codeStr = "header.className = 'videoPodcastBackground'; "; + break; + + case Ci.nsIFeed.TYPE_AUDIO: + codeStr = "header.className = 'audioPodcastBackground'; "; + break; + + default: + codeStr = "header.className = 'feedBackground'; "; + 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 = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType), + Ci.nsILocalFile); + + if (this._selectedApp.exists()) + this._initMenuItemWithFile(menuItem, this._selectedApp); + else { + // Hide the menuitem if the last selected application doesn't exist + menuItem.setAttribute("hidden", true); + } + } + catch(ex) { + // Hide the menuitem until an application is selected + menuItem.setAttribute("hidden", true); + } + this._contentSandbox.handlersMenuPopup = handlersMenuPopup; + this._contentSandbox.selectedAppMenuItem = menuItem; + + codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); "; + + // List the default feed reader + try { + this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"]. + getService(Ci.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) { menuItem = null; /* no default reader */ } + + 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); "; + + Cu.evalInSandbox(codeStr, this._contentSandbox); + + var historySvc = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + historySvc.addObserver(this, false); + + // List of web handlers + var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. + getService(Ci.nsIWebContentConverterService); + var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType), {}); + if (handlers.length != 0) { + for (var 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);"; + Cu.evalInSandbox(codeStr, this._contentSandbox); + + // For privacy reasons we cannot set the image attribute directly + // to the icon url, see Bug 358878 + var uri = makeURI(handlers[i].uri); + if (!this._setFaviconForWebReader(uri, menuItem)) { + if (uri && /^https?/.test(uri.scheme)) { + var iconURL = makeURI(uri.prePath + "/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 Ci.nsIFeed.TYPE_VIDEO: + textfeedinfo1 = "feedSubscriptionVideoPodcast1"; + textfeedinfo2 = "feedSubscriptionVideoPodcast2"; + break; + case Ci.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.value = feedinfo1Str; " + + "feedinfo2.value = feedinfo2Str; " + + "header.setAttribute('firstrun', 'true');" + Cu.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 BrowserSnowlRiverWriter + */ + _getOriginalURI: function FW__getOriginalURI(aWindow) { + var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor). + getInterface(Ci.nsIWebNavigation). + QueryInterface(Ci.nsIDocShell).currentDocumentChannel; + + var uri = makeURI(SUBSCRIBE_PAGE_URI); + var resolvedURI = Cc["@mozilla.org/chrome/chrome-registry;1"]. + getService(Ci.nsIChromeRegistry). + convertChromeURL(uri); + + if (resolvedURI.equals(chan.URI)) + return chan.originalURI; + + return null; + }, + + _window: null, + _document: null, + _feedURI: null, + _feedPrincipal: null, + + // nsISnowlRiverWriter + init: function FW_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 = Cc["@mozilla.org/scriptsecuritymanager;1"]. + getService(Ci.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 = 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_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 FW_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 FW_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 = 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_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 = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + historySvc.removeObserver(this); + }, + + _removeFeedFromCache: function FW__removeFeedFromCache() { + if (this._feedURI) { + var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"]. + getService(Ci.nsIFeedResultService); + feedService.removeFeedResult(this._feedURI); + this._feedURI = null; + } + }, + + subscribe: function FW_subscribe() { + var feedType = this._getFeedType(); + + // Subscribe to the feed using the selected handler and save prefs + var prefs = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.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 choosen 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 = Cc["@mozilla.org/supports-string;1"]. + createInstance(Ci.nsISupportsString); + supportsString.data = webURI; + prefs.setComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString, + supportsString); + + var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. + getService(Ci.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); + } + } + else { + switch (selectedItem.id) { + case "selectedAppMenuItem": + prefs.setCharPref(getPrefReaderForType(feedType), "client"); + prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, + this._selectedApp); + break; + case "defaultHandlerMenuItem": + prefs.setCharPref(getPrefReaderForType(feedType), "client"); + prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, + this._defaultSystemReader); + break; + case "liveBookmarksMenuItem": + defaultHandler = "bookmarks"; + prefs.setCharPref(getPrefReaderForType(feedType), "bookmarks"); + break; + } + var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"]. + getService(Ci.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 "bookmarks" (if the live bookmarks 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 FW_observe(subject, topic, data) { + if (!this._window) { + // this._window is null unless this.write 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 FW__setFaviconForWebReader(aURI, aMenuItem) { + var faviconsSvc = this._faviconService; + var faviconURL = null; + try { + faviconURL = faviconsSvc.getFaviconForPage(aURI); + } + catch(ex) { } + + if (faviconURL) { + var mimeType = { }; + var bytes = faviconsSvc.getFaviconData(faviconURL, mimeType, + { /* dataLen */ }); + if (bytes) { + var dataURI = "data:" + mimeType.value + ";" + "base64," + + btoa(String.fromCharCode.apply(null, bytes)); + + this._contentSandbox.menuItem = aMenuItem; + this._contentSandbox.dataURI = dataURI; + var codeStr = "menuItem.setAttribute('image', dataURI);"; + Cu.evalInSandbox(codeStr, this._contentSandbox); + this._contentSandbox.menuItem = null; + this._contentSandbox.dataURI = null; + + return true; + } + } + + return false; + }, + + // nsINavHistoryService + onPageChanged: function FW_onPageChanged(aURI, aWhat, aValue) { + if (aWhat == Ci.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 (var 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() { }, + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, + Ci.nsIObserver, + Ci.nsINavHistoryObserver]) +};