browser/components/feeds/src/FeedWriter.js

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

     1 # -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 # This Source Code Form is subject to the terms of the Mozilla Public
     3 # License, v. 2.0. If a copy of the MPL was not distributed with this
     4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6 const Cc = Components.classes;
     7 const Ci = Components.interfaces;
     8 const Cr = Components.results;
     9 const Cu = Components.utils;
    11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    13 const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
    14 const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
    16 function LOG(str) {
    17   var prefB = Cc["@mozilla.org/preferences-service;1"].
    18               getService(Ci.nsIPrefBranch);
    20   var shouldLog = false;
    21   try {
    22     shouldLog = prefB.getBoolPref("feeds.log");
    23   } 
    24   catch (ex) {
    25   }
    27   if (shouldLog)
    28     dump("*** Feeds: " + str + "\n");
    29 }
    31 /**
    32  * Wrapper function for nsIIOService::newURI.
    33  * @param aURLSpec
    34  *        The URL string from which to create an nsIURI.
    35  * @returns an nsIURI object, or null if the creation of the URI failed.
    36  */
    37 function makeURI(aURLSpec, aCharset) {
    38   var ios = Cc["@mozilla.org/network/io-service;1"].
    39             getService(Ci.nsIIOService);
    40   try {
    41     return ios.newURI(aURLSpec, aCharset, null);
    42   } catch (ex) { }
    44   return null;
    45 }
    47 const XML_NS = "http://www.w3.org/XML/1998/namespace";
    48 const HTML_NS = "http://www.w3.org/1999/xhtml";
    49 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    50 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
    51 const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
    52 const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
    53 const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
    55 const PREF_SELECTED_APP = "browser.feeds.handlers.application";
    56 const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
    57 const PREF_SELECTED_ACTION = "browser.feeds.handler";
    58 const PREF_SELECTED_READER = "browser.feeds.handler.default";
    60 const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
    61 const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
    62 const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
    63 const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
    65 const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
    66 const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
    67 const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
    68 const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
    70 const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
    72 const TITLE_ID = "feedTitleText";
    73 const SUBTITLE_ID = "feedSubtitleText";
    75 function getPrefAppForType(t) {
    76   switch (t) {
    77     case Ci.nsIFeed.TYPE_VIDEO:
    78       return PREF_VIDEO_SELECTED_APP;
    80     case Ci.nsIFeed.TYPE_AUDIO:
    81       return PREF_AUDIO_SELECTED_APP;
    83     default:
    84       return PREF_SELECTED_APP;
    85   }
    86 }
    88 function getPrefWebForType(t) {
    89   switch (t) {
    90     case Ci.nsIFeed.TYPE_VIDEO:
    91       return PREF_VIDEO_SELECTED_WEB;
    93     case Ci.nsIFeed.TYPE_AUDIO:
    94       return PREF_AUDIO_SELECTED_WEB;
    96     default:
    97       return PREF_SELECTED_WEB;
    98   }
    99 }
   101 function getPrefActionForType(t) {
   102   switch (t) {
   103     case Ci.nsIFeed.TYPE_VIDEO:
   104       return PREF_VIDEO_SELECTED_ACTION;
   106     case Ci.nsIFeed.TYPE_AUDIO:
   107       return PREF_AUDIO_SELECTED_ACTION;
   109     default:
   110       return PREF_SELECTED_ACTION;
   111   }
   112 }
   114 function getPrefReaderForType(t) {
   115   switch (t) {
   116     case Ci.nsIFeed.TYPE_VIDEO:
   117       return PREF_VIDEO_SELECTED_READER;
   119     case Ci.nsIFeed.TYPE_AUDIO:
   120       return PREF_AUDIO_SELECTED_READER;
   122     default:
   123       return PREF_SELECTED_READER;
   124   }
   125 }
   127 /**
   128  * Converts a number of bytes to the appropriate unit that results in a
   129  * number that needs fewer than 4 digits
   130  *
   131  * @return a pair: [new value with 3 sig. figs., its unit]
   132   */
   133 function convertByteUnits(aBytes) {
   134   var units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
   135   let unitIndex = 0;
   137   // convert to next unit if it needs 4 digits (after rounding), but only if
   138   // we know the name of the next unit
   139   while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
   140     aBytes /= 1024;
   141     unitIndex++;
   142   }
   144   // Get rid of insignificant bits by truncating to 1 or 0 decimal points
   145   // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
   146   aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
   148   return [aBytes, units[unitIndex]];
   149 }
   151 function FeedWriter() {}
   152 FeedWriter.prototype = {
   153   _mimeSvc      : Cc["@mozilla.org/mime;1"].
   154                   getService(Ci.nsIMIMEService),
   156   _getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
   157     return container.fields.getProperty(property).
   158                      QueryInterface(Ci.nsIPropertyBag2);
   159   },
   161   _getPropertyAsString: function FW__getPropertyAsString(container, property) {
   162     try {
   163       return container.fields.getPropertyAsAString(property);
   164     }
   165     catch (e) {
   166     }
   167     return "";
   168   },
   170   _setContentText: function FW__setContentText(id, text) {
   171     this._contentSandbox.element = this._document.getElementById(id);
   172     this._contentSandbox.textNode = text.createDocumentFragment(this._contentSandbox.element);
   173     var codeStr =
   174       "while (element.hasChildNodes()) " +
   175       "  element.removeChild(element.firstChild);" +
   176       "element.appendChild(textNode);";
   177     if (text.base) {
   178       this._contentSandbox.spec = text.base.spec;
   179       codeStr += "element.setAttributeNS('" + XML_NS + "', 'base', spec);";
   180     }
   181     Cu.evalInSandbox(codeStr, this._contentSandbox);
   182     this._contentSandbox.element = null;
   183     this._contentSandbox.textNode = null;
   184   },
   186   /**
   187    * Safely sets the href attribute on an anchor tag, providing the URI 
   188    * specified can be loaded according to rules. 
   189    * @param   element
   190    *          The element to set a URI attribute on
   191    * @param   attribute
   192    *          The attribute of the element to set the URI to, e.g. href or src
   193    * @param   uri
   194    *          The URI spec to set as the href
   195    */
   196   _safeSetURIAttribute: 
   197   function FW__safeSetURIAttribute(element, attribute, uri) {
   198     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
   199                  getService(Ci.nsIScriptSecurityManager);    
   200     const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
   201     try {
   202       secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
   203       // checkLoadURIStrWithPrincipal will throw if the link URI should not be
   204       // loaded, either because our feedURI isn't allowed to load it or per
   205       // the rules specified in |flags|, so we'll never "linkify" the link...
   206     }
   207     catch (e) {
   208       // Not allowed to load this link because secman.checkLoadURIStr threw
   209       return;
   210     }
   212     this._contentSandbox.element = element;
   213     this._contentSandbox.uri = uri;
   214     var codeStr = "element.setAttribute('" + attribute + "', uri);";
   215     Cu.evalInSandbox(codeStr, this._contentSandbox);
   216   },
   218   /**
   219    * Use this sandbox to run any dom manipulation code on nodes which
   220    * are already inserted into the content document.
   221    */
   222   __contentSandbox: null,
   223   get _contentSandbox() {
   224     // This whole sandbox setup is totally archaic. It was introduced in bug
   225     // 360529, presumably before the existence of a solid security membrane,
   226     // since all of the manipulation of content here should be made safe by
   227     // Xrays. And now that anonymous content is no longer content-accessible,
   228     // manipulating the xml stylesheet content can't be done from content
   229     // anymore.
   230     //
   231     // The right solution would be to rip out all of this sandbox junk and
   232     // manipulate the DOM directly. But that's a big yak to shave, so for now,
   233     // we just give the sandbox an nsExpandedPrincipal with []. This has the
   234     // effect of giving it Xrays, and making it same-origin with the XBL scope,
   235     // thereby letting it manipulate anonymous content.
   236     if (!this.__contentSandbox)
   237       this.__contentSandbox = new Cu.Sandbox([this._window],
   238                                              {sandboxName: 'FeedWriter'});
   240     return this.__contentSandbox;
   241   },
   243   /**
   244    * Calls doCommand for a given XUL element within the context of the
   245    * content document.
   246    *
   247    * @param aElement
   248    *        the XUL element to call doCommand() on.
   249    */
   250   _safeDoCommand: function FW___safeDoCommand(aElement) {
   251     this._contentSandbox.element = aElement;
   252     Cu.evalInSandbox("element.doCommand();", this._contentSandbox);
   253     this._contentSandbox.element = null;
   254   },
   256   __faviconService: null,
   257   get _faviconService() {
   258     if (!this.__faviconService)
   259       this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
   260                               getService(Ci.nsIFaviconService);
   262     return this.__faviconService;
   263   },
   265   __bundle: null,
   266   get _bundle() {
   267     if (!this.__bundle) {
   268       this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"].
   269                       getService(Ci.nsIStringBundleService).
   270                       createBundle(URI_BUNDLE);
   271     }
   272     return this.__bundle;
   273   },
   275   _getFormattedString: function FW__getFormattedString(key, params) {
   276     return this._bundle.formatStringFromName(key, params, params.length);
   277   },
   279   _getString: function FW__getString(key) {
   280     return this._bundle.GetStringFromName(key);
   281   },
   283   /* Magic helper methods to be used instead of xbl properties */
   284   _getSelectedItemFromMenulist: function FW__getSelectedItemFromList(aList) {
   285     var node = aList.firstChild.firstChild;
   286     while (node) {
   287       if (node.localName == "menuitem" && node.getAttribute("selected") == "true")
   288         return node;
   290       node = node.nextSibling;
   291     }
   293     return null;
   294   },
   296   _setCheckboxCheckedState: function FW__setCheckboxCheckedState(aCheckbox, aValue) {
   297     // see checkbox.xml, xbl bindings are not applied within the sandbox!
   298     this._contentSandbox.checkbox = aCheckbox;
   299     var codeStr;
   300     var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
   301     if (aValue)
   302       codeStr = "checkbox.setAttribute('checked', 'true'); ";
   303     else
   304       codeStr = "checkbox.removeAttribute('checked'); ";
   306     if (change) {
   307       this._contentSandbox.document = this._document;
   308       codeStr += "var event = document.createEvent('Events'); " +
   309                  "event.initEvent('CheckboxStateChange', true, true);" +
   310                  "checkbox.dispatchEvent(event);"
   311     }
   313     Cu.evalInSandbox(codeStr, this._contentSandbox);
   314   },
   316    /**
   317    * Returns a date suitable for displaying in the feed preview. 
   318    * If the date cannot be parsed, the return value is "false".
   319    * @param   dateString
   320    *          A date as extracted from a feed entry. (entry.updated)
   321    */
   322   _parseDate: function FW__parseDate(dateString) {
   323     // Convert the date into the user's local time zone
   324     dateObj = new Date(dateString);
   326     // Make sure the date we're given is valid.
   327     if (!dateObj.getTime())
   328       return false;
   330     var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
   331                       getService(Ci.nsIScriptableDateFormat);
   332     return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
   333                                       dateObj.getFullYear(), dateObj.getMonth()+1, dateObj.getDate(),
   334                                       dateObj.getHours(), dateObj.getMinutes(), dateObj.getSeconds());
   335   },
   337   /**
   338    * Returns the feed type.
   339    */
   340   __feedType: null,
   341   _getFeedType: function FW__getFeedType() {
   342     if (this.__feedType != null)
   343       return this.__feedType;
   345     try {
   346       // grab the feed because it's got the feed.type in it.
   347       var container = this._getContainer();
   348       var feed = container.QueryInterface(Ci.nsIFeed);
   349       this.__feedType = feed.type;
   350       return feed.type;
   351     } catch (ex) { }
   353     return Ci.nsIFeed.TYPE_FEED;
   354   },
   356   /**
   357    * Maps a feed type to a maybe-feed mimetype.
   358    */
   359   _getMimeTypeForFeedType: function FW__getMimeTypeForFeedType() {
   360     switch (this._getFeedType()) {
   361       case Ci.nsIFeed.TYPE_VIDEO:
   362         return TYPE_MAYBE_VIDEO_FEED;
   364       case Ci.nsIFeed.TYPE_AUDIO:
   365         return TYPE_MAYBE_AUDIO_FEED;
   367       default:
   368         return TYPE_MAYBE_FEED;
   369     }
   370   },
   372   /**
   373    * Writes the feed title into the preview document.
   374    * @param   container
   375    *          The feed container
   376    */
   377   _setTitleText: function FW__setTitleText(container) {
   378     if (container.title) {
   379       var title = container.title.plainText();
   380       this._setContentText(TITLE_ID, container.title);
   381       this._contentSandbox.document = this._document;
   382       this._contentSandbox.title = title;
   383       var codeStr = "document.title = title;"
   384       Cu.evalInSandbox(codeStr, this._contentSandbox);
   385     }
   387     var feed = container.QueryInterface(Ci.nsIFeed);
   388     if (feed && feed.subtitle)
   389       this._setContentText(SUBTITLE_ID, container.subtitle);
   390   },
   392   /**
   393    * Writes the title image into the preview document if one is present.
   394    * @param   container
   395    *          The feed container
   396    */
   397   _setTitleImage: function FW__setTitleImage(container) {
   398     try {
   399       var parts = container.image;
   401       // Set up the title image (supplied by the feed)
   402       var feedTitleImage = this._document.getElementById("feedTitleImage");
   403       this._safeSetURIAttribute(feedTitleImage, "src", 
   404                                 parts.getPropertyAsAString("url"));
   406       // Set up the title image link
   407       var feedTitleLink = this._document.getElementById("feedTitleLink");
   409       var titleText = this._getFormattedString("linkTitleTextFormat", 
   410                                                [parts.getPropertyAsAString("title")]);
   411       this._contentSandbox.feedTitleLink = feedTitleLink;
   412       this._contentSandbox.titleText = titleText;
   413       this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
   414       this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
   416       // Fix the margin on the main title, so that the image doesn't run over
   417       // the underline
   418       var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
   419                     "feedTitleText.style.marginRight = titleImageWidth + 'px';";
   420       Cu.evalInSandbox(codeStr, this._contentSandbox);
   421       this._contentSandbox.feedTitleLink = null;
   422       this._contentSandbox.titleText = null;
   423       this._contentSandbox.feedTitleText = null;
   424       this._contentSandbox.titleImageWidth = null;
   426       this._safeSetURIAttribute(feedTitleLink, "href", 
   427                                 parts.getPropertyAsAString("link"));
   428     }
   429     catch (e) {
   430       LOG("Failed to set Title Image (this is benign): " + e);
   431     }
   432   },
   434   /**
   435    * Writes all entries contained in the feed.
   436    * @param   container
   437    *          The container of entries in the feed
   438    */
   439   _writeFeedContent: function FW__writeFeedContent(container) {
   440     // Build the actual feed content
   441     var feed = container.QueryInterface(Ci.nsIFeed);
   442     if (feed.items.length == 0)
   443       return;
   445     this._contentSandbox.feedContent =
   446       this._document.getElementById("feedContent");
   448     for (var i = 0; i < feed.items.length; ++i) {
   449       var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
   450       entry.QueryInterface(Ci.nsIFeedContainer);
   452       var entryContainer = this._document.createElementNS(HTML_NS, "div");
   453       entryContainer.className = "entry";
   455       // If the entry has a title, make it a link
   456       if (entry.title) {
   457         var a = this._document.createElementNS(HTML_NS, "a");
   458         var span = this._document.createElementNS(HTML_NS, "span");
   459         a.appendChild(span);
   460         if (entry.title.base)
   461           span.setAttributeNS(XML_NS, "base", entry.title.base.spec);
   462         span.appendChild(entry.title.createDocumentFragment(a));
   464         // Entries are not required to have links, so entry.link can be null.
   465         if (entry.link)
   466           this._safeSetURIAttribute(a, "href", entry.link.spec);
   468         var title = this._document.createElementNS(HTML_NS, "h3");
   469         title.appendChild(a);
   471         var lastUpdated = this._parseDate(entry.updated);
   472         if (lastUpdated) {
   473           var dateDiv = this._document.createElementNS(HTML_NS, "div");
   474           dateDiv.className = "lastUpdated";
   475           dateDiv.textContent = lastUpdated;
   476           title.appendChild(dateDiv);
   477         }
   479         entryContainer.appendChild(title);
   480       }
   482       var body = this._document.createElementNS(HTML_NS, "div");
   483       var summary = entry.summary || entry.content;
   484       var docFragment = null;
   485       if (summary) {
   486         if (summary.base)
   487           body.setAttributeNS(XML_NS, "base", summary.base.spec);
   488         else
   489           LOG("no base?");
   490         docFragment = summary.createDocumentFragment(body);
   491         if (docFragment)
   492           body.appendChild(docFragment);
   494         // If the entry doesn't have a title, append a # permalink
   495         // See http://scripting.com/rss.xml for an example
   496         if (!entry.title && entry.link) {
   497           var a = this._document.createElementNS(HTML_NS, "a");
   498           a.appendChild(this._document.createTextNode("#"));
   499           this._safeSetURIAttribute(a, "href", entry.link.spec);
   500           body.appendChild(this._document.createTextNode(" "));
   501           body.appendChild(a);
   502         }
   504       }
   505       body.className = "feedEntryContent";
   506       entryContainer.appendChild(body);
   508       if (entry.enclosures && entry.enclosures.length > 0) {
   509         var enclosuresDiv = this._buildEnclosureDiv(entry);
   510         entryContainer.appendChild(enclosuresDiv);
   511       }
   513       this._contentSandbox.entryContainer = entryContainer;
   514       this._contentSandbox.clearDiv =
   515         this._document.createElementNS(HTML_NS, "div");
   516       this._contentSandbox.clearDiv.style.clear = "both";
   518       var codeStr = "feedContent.appendChild(entryContainer); " +
   519                      "feedContent.appendChild(clearDiv);"
   520       Cu.evalInSandbox(codeStr, this._contentSandbox);
   521     }
   523     this._contentSandbox.feedContent = null;
   524     this._contentSandbox.entryContainer = null;
   525     this._contentSandbox.clearDiv = null;
   526   },
   528   /**
   529    * Takes a url to a media item and returns the best name it can come up with.
   530    * Frequently this is the filename portion (e.g. passing in 
   531    * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
   532    * cases, this will return the entire url (e.g. passing in
   533    * http://example.com/somedirectory/ would return 
   534    * http://example.com/somedirectory/).
   535    * @param aURL
   536    *        The URL string from which to create a display name
   537    * @returns a string
   538    */
   539   _getURLDisplayName: function FW__getURLDisplayName(aURL) {
   540     var url = makeURI(aURL);
   541     url.QueryInterface(Ci.nsIURL);
   542     if (url == null || url.fileName.length == 0)
   543       return decodeURIComponent(aURL);
   545     return decodeURIComponent(url.fileName);
   546   },
   548   /**
   549    * Takes a FeedEntry with enclosures, generates the HTML code to represent
   550    * them, and returns that.
   551    * @param   entry
   552    *          FeedEntry with enclosures
   553    * @returns element
   554    */
   555   _buildEnclosureDiv: function FW__buildEnclosureDiv(entry) {
   556     var enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
   557     enclosuresDiv.className = "enclosures";
   559     enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
   561     var roundme = function(n) {
   562       return (Math.round(n * 100) / 100).toLocaleString();
   563     }
   565     for (var i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
   566       var enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2);
   568       if (!(enc.hasKey("url"))) 
   569         continue;
   571       var enclosureDiv = this._document.createElementNS(HTML_NS, "div");
   572       enclosureDiv.setAttribute("class", "enclosure");
   574       var mozicon = "moz-icon://.txt?size=16";
   575       var type_text = null;
   576       var size_text = null;
   578       if (enc.hasKey("type")) {
   579         type_text = enc.get("type");
   580         try {
   581           var handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
   583           if (handlerInfoWrapper)
   584             type_text = handlerInfoWrapper.description;
   586           if  (type_text && type_text.length > 0)
   587             mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
   589         } catch (ex) { }
   591       }
   593       if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
   594         var enc_size = convertByteUnits(parseInt(enc.get("length")));
   596         var size_text = this._getFormattedString("enclosureSizeText", 
   597                              [enc_size[0], this._getString(enc_size[1])]);
   598       }
   600       var iconimg = this._document.createElementNS(HTML_NS, "img");
   601       iconimg.setAttribute("src", mozicon);
   602       iconimg.setAttribute("class", "type-icon");
   603       enclosureDiv.appendChild(iconimg);
   605       enclosureDiv.appendChild(this._document.createTextNode( " " ));
   607       var enc_href = this._document.createElementNS(HTML_NS, "a");
   608       enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
   609       this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
   610       enclosureDiv.appendChild(enc_href);
   612       if (type_text && size_text)
   613         enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
   615       else if (type_text) 
   616         enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
   618       else if (size_text)
   619         enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
   621       enclosuresDiv.appendChild(enclosureDiv);
   622     }
   624     return enclosuresDiv;
   625   },
   627   /**
   628    * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
   629    * Displays error information if there was one.
   630    * @param   result
   631    *          The parsed feed result
   632    * @returns A valid nsIFeedContainer object containing the contents of
   633    *          the feed.
   634    */
   635   _getContainer: function FW__getContainer(result) {
   636     var feedService = 
   637         Cc["@mozilla.org/browser/feeds/result-service;1"].
   638         getService(Ci.nsIFeedResultService);
   640     try {
   641       var result = 
   642         feedService.getFeedResult(this._getOriginalURI(this._window));
   643     }
   644     catch (e) {
   645       LOG("Subscribe Preview: feed not available?!");
   646     }
   648     if (result.bozo) {
   649       LOG("Subscribe Preview: feed result is bozo?!");
   650     }
   652     try {
   653       var container = result.doc;
   654     }
   655     catch (e) {
   656       LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
   657       return null;
   658     }
   659     return container;
   660   },
   662   /**
   663    * Get the human-readable display name of a file. This could be the 
   664    * application name.
   665    * @param   file
   666    *          A nsIFile to look up the name of
   667    * @returns The display name of the application represented by the file.
   668    */
   669   _getFileDisplayName: function FW__getFileDisplayName(file) {
   670 #ifdef XP_WIN
   671     if (file instanceof Ci.nsILocalFileWin) {
   672       try {
   673         return file.getVersionInfoField("FileDescription");
   674       } catch (e) {}
   675     }
   676 #endif
   677 #ifdef XP_MACOSX
   678     if (file instanceof Ci.nsILocalFileMac) {
   679       try {
   680         return file.bundleDisplayName;
   681       } catch (e) {}
   682     }
   683 #endif
   684     return file.leafName;
   685   },
   687   /**
   688    * Get moz-icon url for a file
   689    * @param   file
   690    *          A nsIFile object for which the moz-icon:// is returned
   691    * @returns moz-icon url of the given file as a string
   692    */
   693   _getFileIconURL: function FW__getFileIconURL(file) {
   694     var ios = Cc["@mozilla.org/network/io-service;1"].
   695               getService(Ci.nsIIOService);
   696     var fph = ios.getProtocolHandler("file")
   697                  .QueryInterface(Ci.nsIFileProtocolHandler);
   698     var urlSpec = fph.getURLSpecFromFile(file);
   699     return "moz-icon://" + urlSpec + "?size=16";
   700   },
   702   /**
   703    * Helper method to set the selected application and system default
   704    * reader menuitems details from a file object
   705    *   @param aMenuItem
   706    *          The menuitem on which the attributes should be set
   707    *   @param aFile
   708    *          The menuitem's associated file
   709    */
   710   _initMenuItemWithFile: function(aMenuItem, aFile) {
   711     this._contentSandbox.menuitem = aMenuItem;
   712     this._contentSandbox.label = this._getFileDisplayName(aFile);
   713     this._contentSandbox.image = this._getFileIconURL(aFile);
   714     var codeStr = "menuitem.setAttribute('label', label); " +
   715                   "menuitem.setAttribute('image', image);"
   716     Cu.evalInSandbox(codeStr, this._contentSandbox);
   717   },
   719   /**
   720    * Helper method to get an element in the XBL binding where the handler
   721    * selection UI lives
   722    */
   723   _getUIElement: function FW__getUIElement(id) {
   724     return this._document.getAnonymousElementByAttribute(
   725       this._document.getElementById("feedSubscribeLine"), "anonid", id);
   726   },
   728   /**
   729    * Displays a prompt from which the user may choose a (client) feed reader.
   730    * @param aCallback the callback method, passes in true if a feed reader was
   731    *        selected, false otherwise.
   732    */
   733   _chooseClientApp: function FW__chooseClientApp(aCallback) {
   734     try {
   735       let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   736       let fpCallback = function fpCallback_done(aResult) {
   737         if (aResult == Ci.nsIFilePicker.returnOK) {
   738           this._selectedApp = fp.file;
   739           if (this._selectedApp) {
   740             // XXXben - we need to compare this with the running instance
   741             //          executable just don't know how to do that via script
   742             // XXXmano TBD: can probably add this to nsIShellService
   743 #ifdef XP_WIN
   744 #expand             if (fp.file.leafName != "__MOZ_APP_NAME__.exe") {
   745 #else
   746 #ifdef XP_MACOSX
   747 #expand             if (fp.file.leafName != "__MOZ_MACBUNDLE_NAME__") {
   748 #else
   749 #expand             if (fp.file.leafName != "__MOZ_APP_NAME__-bin") {
   750 #endif
   751 #endif
   752               this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
   753                                          this._selectedApp);
   755               // Show and select the selected application menuitem
   756               let codeStr = "selectedAppMenuItem.hidden = false;" +
   757                             "selectedAppMenuItem.doCommand();"
   758               Cu.evalInSandbox(codeStr, this._contentSandbox);
   759               if (aCallback) {
   760                 aCallback(true);
   761                 return;
   762               }
   763             }
   764           }
   765         }
   766         if (aCallback) {
   767           aCallback(false);
   768         }
   769       }.bind(this);
   771       fp.init(this._window, this._getString("chooseApplicationDialogTitle"),
   772               Ci.nsIFilePicker.modeOpen);
   773       fp.appendFilters(Ci.nsIFilePicker.filterApps);
   774       fp.open(fpCallback);
   775     } catch(ex) {
   776     }
   777   },
   779   _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) {
   780     var checkbox = this._getUIElement("alwaysUse");
   781     if (checkbox) {
   782       var alwaysUse = false;
   783       try {
   784         var prefs = Cc["@mozilla.org/preferences-service;1"].
   785                     getService(Ci.nsIPrefBranch);
   786         if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
   787           alwaysUse = true;
   788       }
   789       catch(ex) { }
   790       this._setCheckboxCheckedState(checkbox, alwaysUse);
   791     }
   792   },
   794   _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() {
   795     var stringLabel = "subscribeFeedUsing";
   796     switch (this._getFeedType()) {
   797       case Ci.nsIFeed.TYPE_VIDEO:
   798         stringLabel = "subscribeVideoPodcastUsing";
   799         break;
   801       case Ci.nsIFeed.TYPE_AUDIO:
   802         stringLabel = "subscribeAudioPodcastUsing";
   803         break;
   804     }
   806     this._contentSandbox.subscribeUsing =
   807       this._getUIElement("subscribeUsingDescription");
   808     this._contentSandbox.label = this._getString(stringLabel);
   809     var codeStr = "subscribeUsing.setAttribute('value', label);"
   810     Cu.evalInSandbox(codeStr, this._contentSandbox);
   811   },
   813   _setAlwaysUseLabel: function FW__setAlwaysUseLabel() {
   814     var checkbox = this._getUIElement("alwaysUse");
   815     if (checkbox) {
   816       if (this._handlersMenuList) {
   817         var handlerName = this._getSelectedItemFromMenulist(this._handlersMenuList)
   818                               .getAttribute("label");
   819         var stringLabel = "alwaysUseForFeeds";
   820         switch (this._getFeedType()) {
   821           case Ci.nsIFeed.TYPE_VIDEO:
   822             stringLabel = "alwaysUseForVideoPodcasts";
   823             break;
   825           case Ci.nsIFeed.TYPE_AUDIO:
   826             stringLabel = "alwaysUseForAudioPodcasts";
   827             break;
   828         }
   830         this._contentSandbox.checkbox = checkbox;
   831         this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
   833         var codeStr = "checkbox.setAttribute('label', label);";
   834         Cu.evalInSandbox(codeStr, this._contentSandbox);
   835       }
   836     }
   837   },
   839   // nsIDomEventListener
   840   handleEvent: function(event) {
   841     if (event.target.ownerDocument != this._document) {
   842       LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
   843       return;
   844     }
   846     if (event.type == "command") {
   847       switch (event.target.getAttribute("anonid")) {
   848         case "subscribeButton":
   849           this.subscribe();
   850           break;
   851         case "chooseApplicationMenuItem":
   852           /* Bug 351263: Make sure to not steal focus if the "Choose
   853            * Application" item is being selected with the keyboard. We do this
   854            * by ignoring command events while the dropdown is closed (user
   855            * arrowing through the combobox), but handling them while the
   856            * combobox dropdown is open (user pressed enter when an item was
   857            * selected). If we don't show the filepicker here, it will be shown
   858            * when clicking "Subscribe Now".
   859            */
   860           var popupbox = this._handlersMenuList.firstChild.boxObject;
   861           popupbox.QueryInterface(Components.interfaces.nsIPopupBoxObject);
   862           if (popupbox.popupState == "hiding") {
   863             this._chooseClientApp(function(aResult) {
   864               if (!aResult) {
   865                 // Select the (per-prefs) selected handler if no application
   866                 // was selected
   867                 this._setSelectedHandler(this._getFeedType());
   868               }
   869             }.bind(this));
   870           }
   871           break;
   872         default:
   873           this._setAlwaysUseLabel();
   874       }
   875     }
   876   },
   878   _setSelectedHandler: function FW__setSelectedHandler(feedType) {
   879     var prefs =   
   880         Cc["@mozilla.org/preferences-service;1"].
   881         getService(Ci.nsIPrefBranch);
   883     var handler = "bookmarks";
   884     try {
   885       handler = prefs.getCharPref(getPrefReaderForType(feedType));
   886     }
   887     catch (ex) { }
   889     switch (handler) {
   890       case "web": {
   891         if (this._handlersMenuList) {
   892           var url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
   893           var handlers =
   894             this._handlersMenuList.getElementsByAttribute("webhandlerurl", url);
   895           if (handlers.length == 0) {
   896             LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
   897             return;
   898           }
   900           this._safeDoCommand(handlers[0]);
   901         }
   902         break;
   903       }
   904       case "client": {
   905         try {
   906           this._selectedApp =
   907             prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
   908         }
   909         catch(ex) {
   910           this._selectedApp = null;
   911         }
   913         if (this._selectedApp) {
   914           this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
   915                                      this._selectedApp);
   916           var codeStr = "selectedAppMenuItem.hidden = false; " +
   917                         "selectedAppMenuItem.doCommand(); ";
   919           // Only show the default reader menuitem if the default reader
   920           // isn't the selected application
   921           if (this._defaultSystemReader) {
   922             var shouldHide =
   923               this._defaultSystemReader.path == this._selectedApp.path;
   924             codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";"
   925           }
   926           Cu.evalInSandbox(codeStr, this._contentSandbox);
   927           break;
   928         }
   929       }
   930       case "bookmarks":
   931       default: {
   932         var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
   933         if (liveBookmarksMenuItem)
   934           this._safeDoCommand(liveBookmarksMenuItem);
   935       } 
   936     }
   937   },
   939   _initSubscriptionUI: function FW__initSubscriptionUI() {
   940     var handlersMenuPopup = this._getUIElement("handlersMenuPopup");
   941     if (!handlersMenuPopup)
   942       return;
   944     var feedType = this._getFeedType();
   945     var codeStr;
   947     // change the background
   948     var header = this._document.getElementById("feedHeader");
   949     this._contentSandbox.header = header;
   950     switch (feedType) {
   951       case Ci.nsIFeed.TYPE_VIDEO:
   952         codeStr = "header.className = 'videoPodcastBackground'; ";
   953         break;
   955       case Ci.nsIFeed.TYPE_AUDIO:
   956         codeStr = "header.className = 'audioPodcastBackground'; ";
   957         break;
   959       default:
   960         codeStr = "header.className = 'feedBackground'; ";
   961     }
   963     var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
   965     // Last-selected application
   966     var menuItem = liveBookmarksMenuItem.cloneNode(false);
   967     menuItem.removeAttribute("selected");
   968     menuItem.setAttribute("anonid", "selectedAppMenuItem");
   969     menuItem.className = "menuitem-iconic selectedAppMenuItem";
   970     menuItem.setAttribute("handlerType", "client");
   971     try {
   972       var prefs = Cc["@mozilla.org/preferences-service;1"].
   973                   getService(Ci.nsIPrefBranch);
   974       this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
   975                                                 Ci.nsILocalFile);
   977       if (this._selectedApp.exists())
   978         this._initMenuItemWithFile(menuItem, this._selectedApp);
   979       else {
   980         // Hide the menuitem if the last selected application doesn't exist
   981         menuItem.setAttribute("hidden", true);
   982       }
   983     }
   984     catch(ex) {
   985       // Hide the menuitem until an application is selected
   986       menuItem.setAttribute("hidden", true);
   987     }
   988     this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
   989     this._contentSandbox.selectedAppMenuItem = menuItem;
   991     codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
   993     // List the default feed reader
   994     try {
   995       this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"].
   996                                   getService(Ci.nsIShellService).
   997                                   defaultFeedReader;
   998       menuItem = liveBookmarksMenuItem.cloneNode(false);
   999       menuItem.removeAttribute("selected");
  1000       menuItem.setAttribute("anonid", "defaultHandlerMenuItem");
  1001       menuItem.className = "menuitem-iconic defaultHandlerMenuItem";
  1002       menuItem.setAttribute("handlerType", "client");
  1004       this._initMenuItemWithFile(menuItem, this._defaultSystemReader);
  1006       // Hide the default reader item if it points to the same application
  1007       // as the last-selected application
  1008       if (this._selectedApp &&
  1009           this._selectedApp.path == this._defaultSystemReader.path)
  1010         menuItem.hidden = true;
  1012     catch(ex) { menuItem = null; /* no default reader */ }
  1014     if (menuItem) {
  1015       this._contentSandbox.defaultHandlerMenuItem = menuItem;
  1016       codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
  1019     // "Choose Application..." menuitem
  1020     menuItem = liveBookmarksMenuItem.cloneNode(false);
  1021     menuItem.removeAttribute("selected");
  1022     menuItem.setAttribute("anonid", "chooseApplicationMenuItem");
  1023     menuItem.className = "menuitem-iconic chooseApplicationMenuItem";
  1024     menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
  1026     this._contentSandbox.chooseAppMenuItem = menuItem;
  1027     codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
  1029     // separator
  1030     this._contentSandbox.chooseAppSep =
  1031       menuItem = liveBookmarksMenuItem.nextSibling.cloneNode(false);
  1032     codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
  1034     Cu.evalInSandbox(codeStr, this._contentSandbox);
  1036     // List of web handlers
  1037     var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  1038                getService(Ci.nsIWebContentConverterService);
  1039     var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
  1040     if (handlers.length != 0) {
  1041       for (var i = 0; i < handlers.length; ++i) {
  1042         menuItem = liveBookmarksMenuItem.cloneNode(false);
  1043         menuItem.removeAttribute("selected");
  1044         menuItem.className = "menuitem-iconic";
  1045         menuItem.setAttribute("label", handlers[i].name);
  1046         menuItem.setAttribute("handlerType", "web");
  1047         menuItem.setAttribute("webhandlerurl", handlers[i].uri);
  1048         this._contentSandbox.menuItem = menuItem;
  1049         codeStr = "handlersMenuPopup.appendChild(menuItem);";
  1050         Cu.evalInSandbox(codeStr, this._contentSandbox);
  1052         this._setFaviconForWebReader(handlers[i].uri, menuItem);
  1054       this._contentSandbox.menuItem = null;
  1057     this._setSelectedHandler(feedType);
  1059     // "Subscribe using..."
  1060     this._setSubscribeUsingLabel();
  1062     // "Always use..." checkbox initial state
  1063     this._setAlwaysUseCheckedState(feedType);
  1064     this._setAlwaysUseLabel();
  1066     // We update the "Always use.." checkbox label whenever the selected item
  1067     // in the list is changed
  1068     handlersMenuPopup.addEventListener("command", this, false);
  1070     // Set up the "Subscribe Now" button
  1071     this._getUIElement("subscribeButton")
  1072         .addEventListener("command", this, false);
  1074     // first-run ui
  1075     var showFirstRunUI = true;
  1076     try {
  1077       showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
  1079     catch (ex) { }
  1080     if (showFirstRunUI) {
  1081       var textfeedinfo1, textfeedinfo2;
  1082       switch (feedType) {
  1083         case Ci.nsIFeed.TYPE_VIDEO:
  1084           textfeedinfo1 = "feedSubscriptionVideoPodcast1";
  1085           textfeedinfo2 = "feedSubscriptionVideoPodcast2";
  1086           break;
  1087         case Ci.nsIFeed.TYPE_AUDIO:
  1088           textfeedinfo1 = "feedSubscriptionAudioPodcast1";
  1089           textfeedinfo2 = "feedSubscriptionAudioPodcast2";
  1090           break;
  1091         default:
  1092           textfeedinfo1 = "feedSubscriptionFeed1";
  1093           textfeedinfo2 = "feedSubscriptionFeed2";
  1096       this._contentSandbox.feedinfo1 =
  1097         this._document.getElementById("feedSubscriptionInfo1");
  1098       this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
  1099       this._contentSandbox.feedinfo2 =
  1100         this._document.getElementById("feedSubscriptionInfo2");
  1101       this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
  1102       this._contentSandbox.header = header;
  1103       codeStr = "feedinfo1.textContent = feedinfo1Str; " +
  1104                 "feedinfo2.textContent = feedinfo2Str; " +
  1105                 "header.setAttribute('firstrun', 'true');"
  1106       Cu.evalInSandbox(codeStr, this._contentSandbox);
  1107       prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
  1109   },
  1111   /**
  1112    * Returns the original URI object of the feed and ensures that this
  1113    * component is only ever invoked from the preview document.  
  1114    * @param aWindow 
  1115    *        The window of the document invoking the BrowserFeedWriter
  1116    */
  1117   _getOriginalURI: function FW__getOriginalURI(aWindow) {
  1118     var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
  1119                getInterface(Ci.nsIWebNavigation).
  1120                QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
  1122     var resolvedURI = Cc["@mozilla.org/network/io-service;1"].
  1123                       getService(Ci.nsIIOService).
  1124                       newChannel("about:feeds", null, null).URI;
  1126     if (resolvedURI.equals(chan.URI))
  1127       return chan.originalURI;
  1129     return null;
  1130   },
  1132   _window: null,
  1133   _document: null,
  1134   _feedURI: null,
  1135   _feedPrincipal: null,
  1136   _handlersMenuList: null,
  1138   // BrowserFeedWriter WebIDL methods
  1139   init: function FW_init(aWindow) {
  1140     var window = aWindow;
  1141     this._feedURI = this._getOriginalURI(window);
  1142     if (!this._feedURI)
  1143       return;
  1145     this._window = window;
  1146     this._document = window.document;
  1147     this._document.getElementById("feedSubscribeLine").offsetTop;
  1148     this._handlersMenuList = this._getUIElement("handlersMenuList");
  1150     var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1151                  getService(Ci.nsIScriptSecurityManager);
  1152     this._feedPrincipal = secman.getSimpleCodebasePrincipal(this._feedURI);
  1154     LOG("Subscribe Preview: feed uri = " + this._window.location.href);
  1156     // Set up the subscription UI
  1157     this._initSubscriptionUI();
  1158     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1159                 getService(Ci.nsIPrefBranch);
  1160     prefs.addObserver(PREF_SELECTED_ACTION, this, false);
  1161     prefs.addObserver(PREF_SELECTED_READER, this, false);
  1162     prefs.addObserver(PREF_SELECTED_WEB, this, false);
  1163     prefs.addObserver(PREF_SELECTED_APP, this, false);
  1164     prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
  1165     prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
  1166     prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
  1167     prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
  1169     prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
  1170     prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
  1171     prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
  1172     prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
  1173   },
  1175   writeContent: function FW_writeContent() {
  1176     if (!this._window)
  1177       return;
  1179     try {
  1180       // Set up the feed content
  1181       var container = this._getContainer();
  1182       if (!container)
  1183         return;
  1185       this._setTitleText(container);
  1186       this._setTitleImage(container);
  1187       this._writeFeedContent(container);
  1189     finally {
  1190       this._removeFeedFromCache();
  1192   },
  1194   close: function FW_close() {
  1195     this._getUIElement("handlersMenuPopup")
  1196         .removeEventListener("command", this, false);
  1197     this._getUIElement("subscribeButton")
  1198         .removeEventListener("command", this, false);
  1199     this._document = null;
  1200     this._window = null;
  1201     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1202                 getService(Ci.nsIPrefBranch);
  1203     prefs.removeObserver(PREF_SELECTED_ACTION, this);
  1204     prefs.removeObserver(PREF_SELECTED_READER, this);
  1205     prefs.removeObserver(PREF_SELECTED_WEB, this);
  1206     prefs.removeObserver(PREF_SELECTED_APP, this);
  1207     prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
  1208     prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
  1209     prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
  1210     prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
  1212     prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
  1213     prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
  1214     prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
  1215     prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
  1217     this._removeFeedFromCache();
  1218     this.__faviconService = null;
  1219     this.__bundle = null;
  1220     this._feedURI = null;
  1221     this.__contentSandbox = null;
  1222   },
  1224   _removeFeedFromCache: function FW__removeFeedFromCache() {
  1225     if (this._feedURI) {
  1226       var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1227                         getService(Ci.nsIFeedResultService);
  1228       feedService.removeFeedResult(this._feedURI);
  1229       this._feedURI = null;
  1231   },
  1233   subscribe: function FW_subscribe() {
  1234     var feedType = this._getFeedType();
  1236     // Subscribe to the feed using the selected handler and save prefs
  1237     var prefs = Cc["@mozilla.org/preferences-service;1"].
  1238                 getService(Ci.nsIPrefBranch);
  1239     var defaultHandler = "reader";
  1240     var useAsDefault = this._getUIElement("alwaysUse").getAttribute("checked");
  1242     var selectedItem = this._getSelectedItemFromMenulist(this._handlersMenuList);
  1243     let subscribeCallback = function() {
  1244       if (selectedItem.hasAttribute("webhandlerurl")) {
  1245         var webURI = selectedItem.getAttribute("webhandlerurl");
  1246         prefs.setCharPref(getPrefReaderForType(feedType), "web");
  1248         var supportsString = Cc["@mozilla.org/supports-string;1"].
  1249                              createInstance(Ci.nsISupportsString);
  1250         supportsString.data = webURI;
  1251         prefs.setComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString,
  1252                               supportsString);
  1254         var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  1255                    getService(Ci.nsIWebContentConverterService);
  1256         var handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
  1257         if (handler) {
  1258           if (useAsDefault) {
  1259             wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
  1262           this._window.location.href = handler.getHandlerURI(this._window.location.href);
  1264       } else {
  1265         switch (selectedItem.getAttribute("anonid")) {
  1266           case "selectedAppMenuItem":
  1267             prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1268                                   this._selectedApp);
  1269             prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1270             break;
  1271           case "defaultHandlerMenuItem":
  1272             prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1273                                   this._defaultSystemReader);
  1274             prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1275             break;
  1276           case "liveBookmarksMenuItem":
  1277             defaultHandler = "bookmarks";
  1278             prefs.setCharPref(getPrefReaderForType(feedType), "bookmarks");
  1279             break;
  1281         var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1282                           getService(Ci.nsIFeedResultService);
  1284         // Pull the title and subtitle out of the document
  1285         var feedTitle = this._document.getElementById(TITLE_ID).textContent;
  1286         var feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
  1287         feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType);
  1290       // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
  1291       // to either "reader" (If a web reader or if an application is selected),
  1292       // or to "bookmarks" (if the live bookmarks option is selected).
  1293       // Otherwise, we should set it to "ask"
  1294       if (useAsDefault) {
  1295         prefs.setCharPref(getPrefActionForType(feedType), defaultHandler);
  1296       } else {
  1297         prefs.setCharPref(getPrefActionForType(feedType), "ask");
  1299     }.bind(this);
  1301     // Show the file picker before subscribing if the
  1302     // choose application menuitem was chosen using the keyboard
  1303     if (selectedItem.getAttribute("anonid") == "chooseApplicationMenuItem") {
  1304       this._chooseClientApp(function(aResult) {
  1305         if (aResult) {
  1306           selectedItem =
  1307             this._getSelectedItemFromMenulist(this._handlersMenuList);
  1308           subscribeCallback();
  1310       }.bind(this));
  1311     } else {
  1312       subscribeCallback();
  1314   },
  1316   // nsIObserver
  1317   observe: function FW_observe(subject, topic, data) {
  1318     if (!this._window) {
  1319       // this._window is null unless this.init was called with a trusted
  1320       // window object.
  1321       return;
  1324     var feedType = this._getFeedType();
  1326     if (topic == "nsPref:changed") {
  1327       switch (data) {
  1328         case PREF_SELECTED_READER:
  1329         case PREF_SELECTED_WEB:
  1330         case PREF_SELECTED_APP:
  1331         case PREF_VIDEO_SELECTED_READER:
  1332         case PREF_VIDEO_SELECTED_WEB:
  1333         case PREF_VIDEO_SELECTED_APP:
  1334         case PREF_AUDIO_SELECTED_READER:
  1335         case PREF_AUDIO_SELECTED_WEB:
  1336         case PREF_AUDIO_SELECTED_APP:
  1337           this._setSelectedHandler(feedType);
  1338           break;
  1339         case PREF_SELECTED_ACTION:
  1340         case PREF_VIDEO_SELECTED_ACTION:
  1341         case PREF_AUDIO_SELECTED_ACTION:
  1342           this._setAlwaysUseCheckedState(feedType);
  1345   },
  1347   /**
  1348    * Sets the icon for the given web-reader item in the readers menu.
  1349    * The icon is fetched and stored through the favicon service.
  1351    * @param aReaderUrl
  1352    *        the reader url.
  1353    * @param aMenuItem
  1354    *        the reader item in the readers menulist.
  1356    * @note For privacy reasons we cannot set the image attribute directly
  1357    *       to the icon url.  See Bug 358878 for details.
  1358    */
  1359   _setFaviconForWebReader:
  1360   function FW__setFaviconForWebReader(aReaderUrl, aMenuItem) {
  1361     var readerURI = makeURI(aReaderUrl);
  1362     if (!/^https?$/.test(readerURI.scheme)) {
  1363       // Don't try to get a favicon for non http(s) URIs.
  1364       return;
  1366     var faviconURI = makeURI(readerURI.prePath + "/favicon.ico");
  1367     var self = this;
  1368     var usePrivateBrowsing = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
  1369                                          .getInterface(Ci.nsIWebNavigation)
  1370                                          .QueryInterface(Ci.nsIDocShell)
  1371                                          .QueryInterface(Ci.nsILoadContext)
  1372                                          .usePrivateBrowsing;
  1373     this._faviconService.setAndFetchFaviconForPage(readerURI, faviconURI, false,
  1374       usePrivateBrowsing ? this._faviconService.FAVICON_LOAD_PRIVATE
  1375                          : this._faviconService.FAVICON_LOAD_NON_PRIVATE,
  1376       function (aURI, aDataLen, aData, aMimeType) {
  1377         if (aDataLen > 0) {
  1378           var dataURL = "data:" + aMimeType + ";base64," +
  1379                         btoa(String.fromCharCode.apply(null, aData));
  1380           self._contentSandbox.menuItem = aMenuItem;
  1381           self._contentSandbox.dataURL = dataURL;
  1382           var codeStr = "menuItem.setAttribute('image', dataURL);";
  1383           Cu.evalInSandbox(codeStr, self._contentSandbox);
  1384           self._contentSandbox.menuItem = null;
  1385           self._contentSandbox.dataURL = null;
  1387       });
  1388   },
  1390   classID: FEEDWRITER_CID,
  1391   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver,
  1392                                          Ci.nsINavHistoryObserver,
  1393                                          Ci.nsIDOMGlobalPropertyInitializer])
  1394 };
  1396 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FeedWriter]);

mercurial