browser/components/feeds/src/FeedWriter.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/feeds/src/FeedWriter.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1396 @@
     1.4 +# -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.8 +
     1.9 +const Cc = Components.classes;
    1.10 +const Ci = Components.interfaces;
    1.11 +const Cr = Components.results;
    1.12 +const Cu = Components.utils;
    1.13 +
    1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +
    1.16 +const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
    1.17 +const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
    1.18 +
    1.19 +function LOG(str) {
    1.20 +  var prefB = Cc["@mozilla.org/preferences-service;1"].
    1.21 +              getService(Ci.nsIPrefBranch);
    1.22 +
    1.23 +  var shouldLog = false;
    1.24 +  try {
    1.25 +    shouldLog = prefB.getBoolPref("feeds.log");
    1.26 +  } 
    1.27 +  catch (ex) {
    1.28 +  }
    1.29 +
    1.30 +  if (shouldLog)
    1.31 +    dump("*** Feeds: " + str + "\n");
    1.32 +}
    1.33 +
    1.34 +/**
    1.35 + * Wrapper function for nsIIOService::newURI.
    1.36 + * @param aURLSpec
    1.37 + *        The URL string from which to create an nsIURI.
    1.38 + * @returns an nsIURI object, or null if the creation of the URI failed.
    1.39 + */
    1.40 +function makeURI(aURLSpec, aCharset) {
    1.41 +  var ios = Cc["@mozilla.org/network/io-service;1"].
    1.42 +            getService(Ci.nsIIOService);
    1.43 +  try {
    1.44 +    return ios.newURI(aURLSpec, aCharset, null);
    1.45 +  } catch (ex) { }
    1.46 +
    1.47 +  return null;
    1.48 +}
    1.49 +
    1.50 +const XML_NS = "http://www.w3.org/XML/1998/namespace";
    1.51 +const HTML_NS = "http://www.w3.org/1999/xhtml";
    1.52 +const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
    1.53 +const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
    1.54 +const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
    1.55 +const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
    1.56 +const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
    1.57 +
    1.58 +const PREF_SELECTED_APP = "browser.feeds.handlers.application";
    1.59 +const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
    1.60 +const PREF_SELECTED_ACTION = "browser.feeds.handler";
    1.61 +const PREF_SELECTED_READER = "browser.feeds.handler.default";
    1.62 +
    1.63 +const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
    1.64 +const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
    1.65 +const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
    1.66 +const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
    1.67 +
    1.68 +const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
    1.69 +const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
    1.70 +const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
    1.71 +const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
    1.72 +
    1.73 +const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
    1.74 +
    1.75 +const TITLE_ID = "feedTitleText";
    1.76 +const SUBTITLE_ID = "feedSubtitleText";
    1.77 +
    1.78 +function getPrefAppForType(t) {
    1.79 +  switch (t) {
    1.80 +    case Ci.nsIFeed.TYPE_VIDEO:
    1.81 +      return PREF_VIDEO_SELECTED_APP;
    1.82 +
    1.83 +    case Ci.nsIFeed.TYPE_AUDIO:
    1.84 +      return PREF_AUDIO_SELECTED_APP;
    1.85 +
    1.86 +    default:
    1.87 +      return PREF_SELECTED_APP;
    1.88 +  }
    1.89 +}
    1.90 +
    1.91 +function getPrefWebForType(t) {
    1.92 +  switch (t) {
    1.93 +    case Ci.nsIFeed.TYPE_VIDEO:
    1.94 +      return PREF_VIDEO_SELECTED_WEB;
    1.95 +
    1.96 +    case Ci.nsIFeed.TYPE_AUDIO:
    1.97 +      return PREF_AUDIO_SELECTED_WEB;
    1.98 +
    1.99 +    default:
   1.100 +      return PREF_SELECTED_WEB;
   1.101 +  }
   1.102 +}
   1.103 +
   1.104 +function getPrefActionForType(t) {
   1.105 +  switch (t) {
   1.106 +    case Ci.nsIFeed.TYPE_VIDEO:
   1.107 +      return PREF_VIDEO_SELECTED_ACTION;
   1.108 +
   1.109 +    case Ci.nsIFeed.TYPE_AUDIO:
   1.110 +      return PREF_AUDIO_SELECTED_ACTION;
   1.111 +
   1.112 +    default:
   1.113 +      return PREF_SELECTED_ACTION;
   1.114 +  }
   1.115 +}
   1.116 +
   1.117 +function getPrefReaderForType(t) {
   1.118 +  switch (t) {
   1.119 +    case Ci.nsIFeed.TYPE_VIDEO:
   1.120 +      return PREF_VIDEO_SELECTED_READER;
   1.121 +
   1.122 +    case Ci.nsIFeed.TYPE_AUDIO:
   1.123 +      return PREF_AUDIO_SELECTED_READER;
   1.124 +
   1.125 +    default:
   1.126 +      return PREF_SELECTED_READER;
   1.127 +  }
   1.128 +}
   1.129 +
   1.130 +/**
   1.131 + * Converts a number of bytes to the appropriate unit that results in a
   1.132 + * number that needs fewer than 4 digits
   1.133 + *
   1.134 + * @return a pair: [new value with 3 sig. figs., its unit]
   1.135 +  */
   1.136 +function convertByteUnits(aBytes) {
   1.137 +  var units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
   1.138 +  let unitIndex = 0;
   1.139 + 
   1.140 +  // convert to next unit if it needs 4 digits (after rounding), but only if
   1.141 +  // we know the name of the next unit
   1.142 +  while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
   1.143 +    aBytes /= 1024;
   1.144 +    unitIndex++;
   1.145 +  }
   1.146 + 
   1.147 +  // Get rid of insignificant bits by truncating to 1 or 0 decimal points
   1.148 +  // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
   1.149 +  aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
   1.150 + 
   1.151 +  return [aBytes, units[unitIndex]];
   1.152 +}
   1.153 +
   1.154 +function FeedWriter() {}
   1.155 +FeedWriter.prototype = {
   1.156 +  _mimeSvc      : Cc["@mozilla.org/mime;1"].
   1.157 +                  getService(Ci.nsIMIMEService),
   1.158 +
   1.159 +  _getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
   1.160 +    return container.fields.getProperty(property).
   1.161 +                     QueryInterface(Ci.nsIPropertyBag2);
   1.162 +  },
   1.163 +
   1.164 +  _getPropertyAsString: function FW__getPropertyAsString(container, property) {
   1.165 +    try {
   1.166 +      return container.fields.getPropertyAsAString(property);
   1.167 +    }
   1.168 +    catch (e) {
   1.169 +    }
   1.170 +    return "";
   1.171 +  },
   1.172 +
   1.173 +  _setContentText: function FW__setContentText(id, text) {
   1.174 +    this._contentSandbox.element = this._document.getElementById(id);
   1.175 +    this._contentSandbox.textNode = text.createDocumentFragment(this._contentSandbox.element);
   1.176 +    var codeStr =
   1.177 +      "while (element.hasChildNodes()) " +
   1.178 +      "  element.removeChild(element.firstChild);" +
   1.179 +      "element.appendChild(textNode);";
   1.180 +    if (text.base) {
   1.181 +      this._contentSandbox.spec = text.base.spec;
   1.182 +      codeStr += "element.setAttributeNS('" + XML_NS + "', 'base', spec);";
   1.183 +    }
   1.184 +    Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.185 +    this._contentSandbox.element = null;
   1.186 +    this._contentSandbox.textNode = null;
   1.187 +  },
   1.188 +
   1.189 +  /**
   1.190 +   * Safely sets the href attribute on an anchor tag, providing the URI 
   1.191 +   * specified can be loaded according to rules. 
   1.192 +   * @param   element
   1.193 +   *          The element to set a URI attribute on
   1.194 +   * @param   attribute
   1.195 +   *          The attribute of the element to set the URI to, e.g. href or src
   1.196 +   * @param   uri
   1.197 +   *          The URI spec to set as the href
   1.198 +   */
   1.199 +  _safeSetURIAttribute: 
   1.200 +  function FW__safeSetURIAttribute(element, attribute, uri) {
   1.201 +    var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
   1.202 +                 getService(Ci.nsIScriptSecurityManager);    
   1.203 +    const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
   1.204 +    try {
   1.205 +      secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
   1.206 +      // checkLoadURIStrWithPrincipal will throw if the link URI should not be
   1.207 +      // loaded, either because our feedURI isn't allowed to load it or per
   1.208 +      // the rules specified in |flags|, so we'll never "linkify" the link...
   1.209 +    }
   1.210 +    catch (e) {
   1.211 +      // Not allowed to load this link because secman.checkLoadURIStr threw
   1.212 +      return;
   1.213 +    }
   1.214 +
   1.215 +    this._contentSandbox.element = element;
   1.216 +    this._contentSandbox.uri = uri;
   1.217 +    var codeStr = "element.setAttribute('" + attribute + "', uri);";
   1.218 +    Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.219 +  },
   1.220 +
   1.221 +  /**
   1.222 +   * Use this sandbox to run any dom manipulation code on nodes which
   1.223 +   * are already inserted into the content document.
   1.224 +   */
   1.225 +  __contentSandbox: null,
   1.226 +  get _contentSandbox() {
   1.227 +    // This whole sandbox setup is totally archaic. It was introduced in bug
   1.228 +    // 360529, presumably before the existence of a solid security membrane,
   1.229 +    // since all of the manipulation of content here should be made safe by
   1.230 +    // Xrays. And now that anonymous content is no longer content-accessible,
   1.231 +    // manipulating the xml stylesheet content can't be done from content
   1.232 +    // anymore.
   1.233 +    //
   1.234 +    // The right solution would be to rip out all of this sandbox junk and
   1.235 +    // manipulate the DOM directly. But that's a big yak to shave, so for now,
   1.236 +    // we just give the sandbox an nsExpandedPrincipal with []. This has the
   1.237 +    // effect of giving it Xrays, and making it same-origin with the XBL scope,
   1.238 +    // thereby letting it manipulate anonymous content.
   1.239 +    if (!this.__contentSandbox)
   1.240 +      this.__contentSandbox = new Cu.Sandbox([this._window],
   1.241 +                                             {sandboxName: 'FeedWriter'});
   1.242 +
   1.243 +    return this.__contentSandbox;
   1.244 +  },
   1.245 +
   1.246 +  /**
   1.247 +   * Calls doCommand for a given XUL element within the context of the
   1.248 +   * content document.
   1.249 +   *
   1.250 +   * @param aElement
   1.251 +   *        the XUL element to call doCommand() on.
   1.252 +   */
   1.253 +  _safeDoCommand: function FW___safeDoCommand(aElement) {
   1.254 +    this._contentSandbox.element = aElement;
   1.255 +    Cu.evalInSandbox("element.doCommand();", this._contentSandbox);
   1.256 +    this._contentSandbox.element = null;
   1.257 +  },
   1.258 +
   1.259 +  __faviconService: null,
   1.260 +  get _faviconService() {
   1.261 +    if (!this.__faviconService)
   1.262 +      this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
   1.263 +                              getService(Ci.nsIFaviconService);
   1.264 +
   1.265 +    return this.__faviconService;
   1.266 +  },
   1.267 +
   1.268 +  __bundle: null,
   1.269 +  get _bundle() {
   1.270 +    if (!this.__bundle) {
   1.271 +      this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"].
   1.272 +                      getService(Ci.nsIStringBundleService).
   1.273 +                      createBundle(URI_BUNDLE);
   1.274 +    }
   1.275 +    return this.__bundle;
   1.276 +  },
   1.277 +
   1.278 +  _getFormattedString: function FW__getFormattedString(key, params) {
   1.279 +    return this._bundle.formatStringFromName(key, params, params.length);
   1.280 +  },
   1.281 +  
   1.282 +  _getString: function FW__getString(key) {
   1.283 +    return this._bundle.GetStringFromName(key);
   1.284 +  },
   1.285 +
   1.286 +  /* Magic helper methods to be used instead of xbl properties */
   1.287 +  _getSelectedItemFromMenulist: function FW__getSelectedItemFromList(aList) {
   1.288 +    var node = aList.firstChild.firstChild;
   1.289 +    while (node) {
   1.290 +      if (node.localName == "menuitem" && node.getAttribute("selected") == "true")
   1.291 +        return node;
   1.292 +
   1.293 +      node = node.nextSibling;
   1.294 +    }
   1.295 +
   1.296 +    return null;
   1.297 +  },
   1.298 +
   1.299 +  _setCheckboxCheckedState: function FW__setCheckboxCheckedState(aCheckbox, aValue) {
   1.300 +    // see checkbox.xml, xbl bindings are not applied within the sandbox!
   1.301 +    this._contentSandbox.checkbox = aCheckbox;
   1.302 +    var codeStr;
   1.303 +    var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
   1.304 +    if (aValue)
   1.305 +      codeStr = "checkbox.setAttribute('checked', 'true'); ";
   1.306 +    else
   1.307 +      codeStr = "checkbox.removeAttribute('checked'); ";
   1.308 +
   1.309 +    if (change) {
   1.310 +      this._contentSandbox.document = this._document;
   1.311 +      codeStr += "var event = document.createEvent('Events'); " +
   1.312 +                 "event.initEvent('CheckboxStateChange', true, true);" +
   1.313 +                 "checkbox.dispatchEvent(event);"
   1.314 +    }
   1.315 +
   1.316 +    Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.317 +  },
   1.318 +
   1.319 +   /**
   1.320 +   * Returns a date suitable for displaying in the feed preview. 
   1.321 +   * If the date cannot be parsed, the return value is "false".
   1.322 +   * @param   dateString
   1.323 +   *          A date as extracted from a feed entry. (entry.updated)
   1.324 +   */
   1.325 +  _parseDate: function FW__parseDate(dateString) {
   1.326 +    // Convert the date into the user's local time zone
   1.327 +    dateObj = new Date(dateString);
   1.328 +
   1.329 +    // Make sure the date we're given is valid.
   1.330 +    if (!dateObj.getTime())
   1.331 +      return false;
   1.332 +
   1.333 +    var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
   1.334 +                      getService(Ci.nsIScriptableDateFormat);
   1.335 +    return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
   1.336 +                                      dateObj.getFullYear(), dateObj.getMonth()+1, dateObj.getDate(),
   1.337 +                                      dateObj.getHours(), dateObj.getMinutes(), dateObj.getSeconds());
   1.338 +  },
   1.339 +
   1.340 +  /**
   1.341 +   * Returns the feed type.
   1.342 +   */
   1.343 +  __feedType: null,
   1.344 +  _getFeedType: function FW__getFeedType() {
   1.345 +    if (this.__feedType != null)
   1.346 +      return this.__feedType;
   1.347 +
   1.348 +    try {
   1.349 +      // grab the feed because it's got the feed.type in it.
   1.350 +      var container = this._getContainer();
   1.351 +      var feed = container.QueryInterface(Ci.nsIFeed);
   1.352 +      this.__feedType = feed.type;
   1.353 +      return feed.type;
   1.354 +    } catch (ex) { }
   1.355 +
   1.356 +    return Ci.nsIFeed.TYPE_FEED;
   1.357 +  },
   1.358 +
   1.359 +  /**
   1.360 +   * Maps a feed type to a maybe-feed mimetype.
   1.361 +   */
   1.362 +  _getMimeTypeForFeedType: function FW__getMimeTypeForFeedType() {
   1.363 +    switch (this._getFeedType()) {
   1.364 +      case Ci.nsIFeed.TYPE_VIDEO:
   1.365 +        return TYPE_MAYBE_VIDEO_FEED;
   1.366 +
   1.367 +      case Ci.nsIFeed.TYPE_AUDIO:
   1.368 +        return TYPE_MAYBE_AUDIO_FEED;
   1.369 +
   1.370 +      default:
   1.371 +        return TYPE_MAYBE_FEED;
   1.372 +    }
   1.373 +  },
   1.374 +
   1.375 +  /**
   1.376 +   * Writes the feed title into the preview document.
   1.377 +   * @param   container
   1.378 +   *          The feed container
   1.379 +   */
   1.380 +  _setTitleText: function FW__setTitleText(container) {
   1.381 +    if (container.title) {
   1.382 +      var title = container.title.plainText();
   1.383 +      this._setContentText(TITLE_ID, container.title);
   1.384 +      this._contentSandbox.document = this._document;
   1.385 +      this._contentSandbox.title = title;
   1.386 +      var codeStr = "document.title = title;"
   1.387 +      Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.388 +    }
   1.389 +
   1.390 +    var feed = container.QueryInterface(Ci.nsIFeed);
   1.391 +    if (feed && feed.subtitle)
   1.392 +      this._setContentText(SUBTITLE_ID, container.subtitle);
   1.393 +  },
   1.394 +
   1.395 +  /**
   1.396 +   * Writes the title image into the preview document if one is present.
   1.397 +   * @param   container
   1.398 +   *          The feed container
   1.399 +   */
   1.400 +  _setTitleImage: function FW__setTitleImage(container) {
   1.401 +    try {
   1.402 +      var parts = container.image;
   1.403 +      
   1.404 +      // Set up the title image (supplied by the feed)
   1.405 +      var feedTitleImage = this._document.getElementById("feedTitleImage");
   1.406 +      this._safeSetURIAttribute(feedTitleImage, "src", 
   1.407 +                                parts.getPropertyAsAString("url"));
   1.408 +
   1.409 +      // Set up the title image link
   1.410 +      var feedTitleLink = this._document.getElementById("feedTitleLink");
   1.411 +
   1.412 +      var titleText = this._getFormattedString("linkTitleTextFormat", 
   1.413 +                                               [parts.getPropertyAsAString("title")]);
   1.414 +      this._contentSandbox.feedTitleLink = feedTitleLink;
   1.415 +      this._contentSandbox.titleText = titleText;
   1.416 +      this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
   1.417 +      this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
   1.418 +
   1.419 +      // Fix the margin on the main title, so that the image doesn't run over
   1.420 +      // the underline
   1.421 +      var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
   1.422 +                    "feedTitleText.style.marginRight = titleImageWidth + 'px';";
   1.423 +      Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.424 +      this._contentSandbox.feedTitleLink = null;
   1.425 +      this._contentSandbox.titleText = null;
   1.426 +      this._contentSandbox.feedTitleText = null;
   1.427 +      this._contentSandbox.titleImageWidth = null;
   1.428 +
   1.429 +      this._safeSetURIAttribute(feedTitleLink, "href", 
   1.430 +                                parts.getPropertyAsAString("link"));
   1.431 +    }
   1.432 +    catch (e) {
   1.433 +      LOG("Failed to set Title Image (this is benign): " + e);
   1.434 +    }
   1.435 +  },
   1.436 +
   1.437 +  /**
   1.438 +   * Writes all entries contained in the feed.
   1.439 +   * @param   container
   1.440 +   *          The container of entries in the feed
   1.441 +   */
   1.442 +  _writeFeedContent: function FW__writeFeedContent(container) {
   1.443 +    // Build the actual feed content
   1.444 +    var feed = container.QueryInterface(Ci.nsIFeed);
   1.445 +    if (feed.items.length == 0)
   1.446 +      return;
   1.447 +
   1.448 +    this._contentSandbox.feedContent =
   1.449 +      this._document.getElementById("feedContent");
   1.450 +
   1.451 +    for (var i = 0; i < feed.items.length; ++i) {
   1.452 +      var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
   1.453 +      entry.QueryInterface(Ci.nsIFeedContainer);
   1.454 +
   1.455 +      var entryContainer = this._document.createElementNS(HTML_NS, "div");
   1.456 +      entryContainer.className = "entry";
   1.457 +
   1.458 +      // If the entry has a title, make it a link
   1.459 +      if (entry.title) {
   1.460 +        var a = this._document.createElementNS(HTML_NS, "a");
   1.461 +        var span = this._document.createElementNS(HTML_NS, "span");
   1.462 +        a.appendChild(span);
   1.463 +        if (entry.title.base)
   1.464 +          span.setAttributeNS(XML_NS, "base", entry.title.base.spec);
   1.465 +        span.appendChild(entry.title.createDocumentFragment(a));
   1.466 +
   1.467 +        // Entries are not required to have links, so entry.link can be null.
   1.468 +        if (entry.link)
   1.469 +          this._safeSetURIAttribute(a, "href", entry.link.spec);
   1.470 +
   1.471 +        var title = this._document.createElementNS(HTML_NS, "h3");
   1.472 +        title.appendChild(a);
   1.473 +
   1.474 +        var lastUpdated = this._parseDate(entry.updated);
   1.475 +        if (lastUpdated) {
   1.476 +          var dateDiv = this._document.createElementNS(HTML_NS, "div");
   1.477 +          dateDiv.className = "lastUpdated";
   1.478 +          dateDiv.textContent = lastUpdated;
   1.479 +          title.appendChild(dateDiv);
   1.480 +        }
   1.481 +
   1.482 +        entryContainer.appendChild(title);
   1.483 +      }
   1.484 +
   1.485 +      var body = this._document.createElementNS(HTML_NS, "div");
   1.486 +      var summary = entry.summary || entry.content;
   1.487 +      var docFragment = null;
   1.488 +      if (summary) {
   1.489 +        if (summary.base)
   1.490 +          body.setAttributeNS(XML_NS, "base", summary.base.spec);
   1.491 +        else
   1.492 +          LOG("no base?");
   1.493 +        docFragment = summary.createDocumentFragment(body);
   1.494 +        if (docFragment)
   1.495 +          body.appendChild(docFragment);
   1.496 +
   1.497 +        // If the entry doesn't have a title, append a # permalink
   1.498 +        // See http://scripting.com/rss.xml for an example
   1.499 +        if (!entry.title && entry.link) {
   1.500 +          var a = this._document.createElementNS(HTML_NS, "a");
   1.501 +          a.appendChild(this._document.createTextNode("#"));
   1.502 +          this._safeSetURIAttribute(a, "href", entry.link.spec);
   1.503 +          body.appendChild(this._document.createTextNode(" "));
   1.504 +          body.appendChild(a);
   1.505 +        }
   1.506 +
   1.507 +      }
   1.508 +      body.className = "feedEntryContent";
   1.509 +      entryContainer.appendChild(body);
   1.510 +
   1.511 +      if (entry.enclosures && entry.enclosures.length > 0) {
   1.512 +        var enclosuresDiv = this._buildEnclosureDiv(entry);
   1.513 +        entryContainer.appendChild(enclosuresDiv);
   1.514 +      }
   1.515 +
   1.516 +      this._contentSandbox.entryContainer = entryContainer;
   1.517 +      this._contentSandbox.clearDiv =
   1.518 +        this._document.createElementNS(HTML_NS, "div");
   1.519 +      this._contentSandbox.clearDiv.style.clear = "both";
   1.520 +      
   1.521 +      var codeStr = "feedContent.appendChild(entryContainer); " +
   1.522 +                     "feedContent.appendChild(clearDiv);"
   1.523 +      Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.524 +    }
   1.525 +
   1.526 +    this._contentSandbox.feedContent = null;
   1.527 +    this._contentSandbox.entryContainer = null;
   1.528 +    this._contentSandbox.clearDiv = null;
   1.529 +  },
   1.530 +
   1.531 +  /**
   1.532 +   * Takes a url to a media item and returns the best name it can come up with.
   1.533 +   * Frequently this is the filename portion (e.g. passing in 
   1.534 +   * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
   1.535 +   * cases, this will return the entire url (e.g. passing in
   1.536 +   * http://example.com/somedirectory/ would return 
   1.537 +   * http://example.com/somedirectory/).
   1.538 +   * @param aURL
   1.539 +   *        The URL string from which to create a display name
   1.540 +   * @returns a string
   1.541 +   */
   1.542 +  _getURLDisplayName: function FW__getURLDisplayName(aURL) {
   1.543 +    var url = makeURI(aURL);
   1.544 +    url.QueryInterface(Ci.nsIURL);
   1.545 +    if (url == null || url.fileName.length == 0)
   1.546 +      return decodeURIComponent(aURL);
   1.547 +
   1.548 +    return decodeURIComponent(url.fileName);
   1.549 +  },
   1.550 +
   1.551 +  /**
   1.552 +   * Takes a FeedEntry with enclosures, generates the HTML code to represent
   1.553 +   * them, and returns that.
   1.554 +   * @param   entry
   1.555 +   *          FeedEntry with enclosures
   1.556 +   * @returns element
   1.557 +   */
   1.558 +  _buildEnclosureDiv: function FW__buildEnclosureDiv(entry) {
   1.559 +    var enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
   1.560 +    enclosuresDiv.className = "enclosures";
   1.561 +
   1.562 +    enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
   1.563 +
   1.564 +    var roundme = function(n) {
   1.565 +      return (Math.round(n * 100) / 100).toLocaleString();
   1.566 +    }
   1.567 +
   1.568 +    for (var i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
   1.569 +      var enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2);
   1.570 +
   1.571 +      if (!(enc.hasKey("url"))) 
   1.572 +        continue;
   1.573 +
   1.574 +      var enclosureDiv = this._document.createElementNS(HTML_NS, "div");
   1.575 +      enclosureDiv.setAttribute("class", "enclosure");
   1.576 +
   1.577 +      var mozicon = "moz-icon://.txt?size=16";
   1.578 +      var type_text = null;
   1.579 +      var size_text = null;
   1.580 +
   1.581 +      if (enc.hasKey("type")) {
   1.582 +        type_text = enc.get("type");
   1.583 +        try {
   1.584 +          var handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
   1.585 +
   1.586 +          if (handlerInfoWrapper)
   1.587 +            type_text = handlerInfoWrapper.description;
   1.588 +
   1.589 +          if  (type_text && type_text.length > 0)
   1.590 +            mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
   1.591 +
   1.592 +        } catch (ex) { }
   1.593 +
   1.594 +      }
   1.595 +
   1.596 +      if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
   1.597 +        var enc_size = convertByteUnits(parseInt(enc.get("length")));
   1.598 +
   1.599 +        var size_text = this._getFormattedString("enclosureSizeText", 
   1.600 +                             [enc_size[0], this._getString(enc_size[1])]);
   1.601 +      }
   1.602 +
   1.603 +      var iconimg = this._document.createElementNS(HTML_NS, "img");
   1.604 +      iconimg.setAttribute("src", mozicon);
   1.605 +      iconimg.setAttribute("class", "type-icon");
   1.606 +      enclosureDiv.appendChild(iconimg);
   1.607 +
   1.608 +      enclosureDiv.appendChild(this._document.createTextNode( " " ));
   1.609 +
   1.610 +      var enc_href = this._document.createElementNS(HTML_NS, "a");
   1.611 +      enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
   1.612 +      this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
   1.613 +      enclosureDiv.appendChild(enc_href);
   1.614 +
   1.615 +      if (type_text && size_text)
   1.616 +        enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
   1.617 +
   1.618 +      else if (type_text) 
   1.619 +        enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
   1.620 +
   1.621 +      else if (size_text)
   1.622 +        enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
   1.623 + 
   1.624 +      enclosuresDiv.appendChild(enclosureDiv);
   1.625 +    }
   1.626 +
   1.627 +    return enclosuresDiv;
   1.628 +  },
   1.629 +
   1.630 +  /**
   1.631 +   * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
   1.632 +   * Displays error information if there was one.
   1.633 +   * @param   result
   1.634 +   *          The parsed feed result
   1.635 +   * @returns A valid nsIFeedContainer object containing the contents of
   1.636 +   *          the feed.
   1.637 +   */
   1.638 +  _getContainer: function FW__getContainer(result) {
   1.639 +    var feedService = 
   1.640 +        Cc["@mozilla.org/browser/feeds/result-service;1"].
   1.641 +        getService(Ci.nsIFeedResultService);
   1.642 +
   1.643 +    try {
   1.644 +      var result = 
   1.645 +        feedService.getFeedResult(this._getOriginalURI(this._window));
   1.646 +    }
   1.647 +    catch (e) {
   1.648 +      LOG("Subscribe Preview: feed not available?!");
   1.649 +    }
   1.650 +    
   1.651 +    if (result.bozo) {
   1.652 +      LOG("Subscribe Preview: feed result is bozo?!");
   1.653 +    }
   1.654 +
   1.655 +    try {
   1.656 +      var container = result.doc;
   1.657 +    }
   1.658 +    catch (e) {
   1.659 +      LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
   1.660 +      return null;
   1.661 +    }
   1.662 +    return container;
   1.663 +  },
   1.664 +
   1.665 +  /**
   1.666 +   * Get the human-readable display name of a file. This could be the 
   1.667 +   * application name.
   1.668 +   * @param   file
   1.669 +   *          A nsIFile to look up the name of
   1.670 +   * @returns The display name of the application represented by the file.
   1.671 +   */
   1.672 +  _getFileDisplayName: function FW__getFileDisplayName(file) {
   1.673 +#ifdef XP_WIN
   1.674 +    if (file instanceof Ci.nsILocalFileWin) {
   1.675 +      try {
   1.676 +        return file.getVersionInfoField("FileDescription");
   1.677 +      } catch (e) {}
   1.678 +    }
   1.679 +#endif
   1.680 +#ifdef XP_MACOSX
   1.681 +    if (file instanceof Ci.nsILocalFileMac) {
   1.682 +      try {
   1.683 +        return file.bundleDisplayName;
   1.684 +      } catch (e) {}
   1.685 +    }
   1.686 +#endif
   1.687 +    return file.leafName;
   1.688 +  },
   1.689 +
   1.690 +  /**
   1.691 +   * Get moz-icon url for a file
   1.692 +   * @param   file
   1.693 +   *          A nsIFile object for which the moz-icon:// is returned
   1.694 +   * @returns moz-icon url of the given file as a string
   1.695 +   */
   1.696 +  _getFileIconURL: function FW__getFileIconURL(file) {
   1.697 +    var ios = Cc["@mozilla.org/network/io-service;1"].
   1.698 +              getService(Ci.nsIIOService);
   1.699 +    var fph = ios.getProtocolHandler("file")
   1.700 +                 .QueryInterface(Ci.nsIFileProtocolHandler);
   1.701 +    var urlSpec = fph.getURLSpecFromFile(file);
   1.702 +    return "moz-icon://" + urlSpec + "?size=16";
   1.703 +  },
   1.704 +
   1.705 +  /**
   1.706 +   * Helper method to set the selected application and system default
   1.707 +   * reader menuitems details from a file object
   1.708 +   *   @param aMenuItem
   1.709 +   *          The menuitem on which the attributes should be set
   1.710 +   *   @param aFile
   1.711 +   *          The menuitem's associated file
   1.712 +   */
   1.713 +  _initMenuItemWithFile: function(aMenuItem, aFile) {
   1.714 +    this._contentSandbox.menuitem = aMenuItem;
   1.715 +    this._contentSandbox.label = this._getFileDisplayName(aFile);
   1.716 +    this._contentSandbox.image = this._getFileIconURL(aFile);
   1.717 +    var codeStr = "menuitem.setAttribute('label', label); " +
   1.718 +                  "menuitem.setAttribute('image', image);"
   1.719 +    Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.720 +  },
   1.721 +
   1.722 +  /**
   1.723 +   * Helper method to get an element in the XBL binding where the handler
   1.724 +   * selection UI lives
   1.725 +   */
   1.726 +  _getUIElement: function FW__getUIElement(id) {
   1.727 +    return this._document.getAnonymousElementByAttribute(
   1.728 +      this._document.getElementById("feedSubscribeLine"), "anonid", id);
   1.729 +  },
   1.730 +
   1.731 +  /**
   1.732 +   * Displays a prompt from which the user may choose a (client) feed reader.
   1.733 +   * @param aCallback the callback method, passes in true if a feed reader was
   1.734 +   *        selected, false otherwise.
   1.735 +   */
   1.736 +  _chooseClientApp: function FW__chooseClientApp(aCallback) {
   1.737 +    try {
   1.738 +      let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   1.739 +      let fpCallback = function fpCallback_done(aResult) {
   1.740 +        if (aResult == Ci.nsIFilePicker.returnOK) {
   1.741 +          this._selectedApp = fp.file;
   1.742 +          if (this._selectedApp) {
   1.743 +            // XXXben - we need to compare this with the running instance
   1.744 +            //          executable just don't know how to do that via script
   1.745 +            // XXXmano TBD: can probably add this to nsIShellService
   1.746 +#ifdef XP_WIN
   1.747 +#expand             if (fp.file.leafName != "__MOZ_APP_NAME__.exe") {
   1.748 +#else
   1.749 +#ifdef XP_MACOSX
   1.750 +#expand             if (fp.file.leafName != "__MOZ_MACBUNDLE_NAME__") {
   1.751 +#else
   1.752 +#expand             if (fp.file.leafName != "__MOZ_APP_NAME__-bin") {
   1.753 +#endif
   1.754 +#endif
   1.755 +              this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
   1.756 +                                         this._selectedApp);
   1.757 +
   1.758 +              // Show and select the selected application menuitem
   1.759 +              let codeStr = "selectedAppMenuItem.hidden = false;" +
   1.760 +                            "selectedAppMenuItem.doCommand();"
   1.761 +              Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.762 +              if (aCallback) {
   1.763 +                aCallback(true);
   1.764 +                return;
   1.765 +              }
   1.766 +            }
   1.767 +          }
   1.768 +        }
   1.769 +        if (aCallback) {
   1.770 +          aCallback(false);
   1.771 +        }
   1.772 +      }.bind(this);
   1.773 +
   1.774 +      fp.init(this._window, this._getString("chooseApplicationDialogTitle"),
   1.775 +              Ci.nsIFilePicker.modeOpen);
   1.776 +      fp.appendFilters(Ci.nsIFilePicker.filterApps);
   1.777 +      fp.open(fpCallback);
   1.778 +    } catch(ex) {
   1.779 +    }
   1.780 +  },
   1.781 +
   1.782 +  _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) {
   1.783 +    var checkbox = this._getUIElement("alwaysUse");
   1.784 +    if (checkbox) {
   1.785 +      var alwaysUse = false;
   1.786 +      try {
   1.787 +        var prefs = Cc["@mozilla.org/preferences-service;1"].
   1.788 +                    getService(Ci.nsIPrefBranch);
   1.789 +        if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
   1.790 +          alwaysUse = true;
   1.791 +      }
   1.792 +      catch(ex) { }
   1.793 +      this._setCheckboxCheckedState(checkbox, alwaysUse);
   1.794 +    }
   1.795 +  },
   1.796 +
   1.797 +  _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() {
   1.798 +    var stringLabel = "subscribeFeedUsing";
   1.799 +    switch (this._getFeedType()) {
   1.800 +      case Ci.nsIFeed.TYPE_VIDEO:
   1.801 +        stringLabel = "subscribeVideoPodcastUsing";
   1.802 +        break;
   1.803 +
   1.804 +      case Ci.nsIFeed.TYPE_AUDIO:
   1.805 +        stringLabel = "subscribeAudioPodcastUsing";
   1.806 +        break;
   1.807 +    }
   1.808 +
   1.809 +    this._contentSandbox.subscribeUsing =
   1.810 +      this._getUIElement("subscribeUsingDescription");
   1.811 +    this._contentSandbox.label = this._getString(stringLabel);
   1.812 +    var codeStr = "subscribeUsing.setAttribute('value', label);"
   1.813 +    Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.814 +  },
   1.815 +
   1.816 +  _setAlwaysUseLabel: function FW__setAlwaysUseLabel() {
   1.817 +    var checkbox = this._getUIElement("alwaysUse");
   1.818 +    if (checkbox) {
   1.819 +      if (this._handlersMenuList) {
   1.820 +        var handlerName = this._getSelectedItemFromMenulist(this._handlersMenuList)
   1.821 +                              .getAttribute("label");
   1.822 +        var stringLabel = "alwaysUseForFeeds";
   1.823 +        switch (this._getFeedType()) {
   1.824 +          case Ci.nsIFeed.TYPE_VIDEO:
   1.825 +            stringLabel = "alwaysUseForVideoPodcasts";
   1.826 +            break;
   1.827 +
   1.828 +          case Ci.nsIFeed.TYPE_AUDIO:
   1.829 +            stringLabel = "alwaysUseForAudioPodcasts";
   1.830 +            break;
   1.831 +        }
   1.832 +
   1.833 +        this._contentSandbox.checkbox = checkbox;
   1.834 +        this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
   1.835 +        
   1.836 +        var codeStr = "checkbox.setAttribute('label', label);";
   1.837 +        Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.838 +      }
   1.839 +    }
   1.840 +  },
   1.841 +
   1.842 +  // nsIDomEventListener
   1.843 +  handleEvent: function(event) {
   1.844 +    if (event.target.ownerDocument != this._document) {
   1.845 +      LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
   1.846 +      return;
   1.847 +    }
   1.848 +
   1.849 +    if (event.type == "command") {
   1.850 +      switch (event.target.getAttribute("anonid")) {
   1.851 +        case "subscribeButton":
   1.852 +          this.subscribe();
   1.853 +          break;
   1.854 +        case "chooseApplicationMenuItem":
   1.855 +          /* Bug 351263: Make sure to not steal focus if the "Choose
   1.856 +           * Application" item is being selected with the keyboard. We do this
   1.857 +           * by ignoring command events while the dropdown is closed (user
   1.858 +           * arrowing through the combobox), but handling them while the
   1.859 +           * combobox dropdown is open (user pressed enter when an item was
   1.860 +           * selected). If we don't show the filepicker here, it will be shown
   1.861 +           * when clicking "Subscribe Now".
   1.862 +           */
   1.863 +          var popupbox = this._handlersMenuList.firstChild.boxObject;
   1.864 +          popupbox.QueryInterface(Components.interfaces.nsIPopupBoxObject);
   1.865 +          if (popupbox.popupState == "hiding") {
   1.866 +            this._chooseClientApp(function(aResult) {
   1.867 +              if (!aResult) {
   1.868 +                // Select the (per-prefs) selected handler if no application
   1.869 +                // was selected
   1.870 +                this._setSelectedHandler(this._getFeedType());
   1.871 +              }
   1.872 +            }.bind(this));
   1.873 +          }
   1.874 +          break;
   1.875 +        default:
   1.876 +          this._setAlwaysUseLabel();
   1.877 +      }
   1.878 +    }
   1.879 +  },
   1.880 +
   1.881 +  _setSelectedHandler: function FW__setSelectedHandler(feedType) {
   1.882 +    var prefs =   
   1.883 +        Cc["@mozilla.org/preferences-service;1"].
   1.884 +        getService(Ci.nsIPrefBranch);
   1.885 +
   1.886 +    var handler = "bookmarks";
   1.887 +    try {
   1.888 +      handler = prefs.getCharPref(getPrefReaderForType(feedType));
   1.889 +    }
   1.890 +    catch (ex) { }
   1.891 +
   1.892 +    switch (handler) {
   1.893 +      case "web": {
   1.894 +        if (this._handlersMenuList) {
   1.895 +          var url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
   1.896 +          var handlers =
   1.897 +            this._handlersMenuList.getElementsByAttribute("webhandlerurl", url);
   1.898 +          if (handlers.length == 0) {
   1.899 +            LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
   1.900 +            return;
   1.901 +          }
   1.902 +
   1.903 +          this._safeDoCommand(handlers[0]);
   1.904 +        }
   1.905 +        break;
   1.906 +      }
   1.907 +      case "client": {
   1.908 +        try {
   1.909 +          this._selectedApp =
   1.910 +            prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
   1.911 +        }
   1.912 +        catch(ex) {
   1.913 +          this._selectedApp = null;
   1.914 +        }
   1.915 +
   1.916 +        if (this._selectedApp) {
   1.917 +          this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
   1.918 +                                     this._selectedApp);
   1.919 +          var codeStr = "selectedAppMenuItem.hidden = false; " +
   1.920 +                        "selectedAppMenuItem.doCommand(); ";
   1.921 +
   1.922 +          // Only show the default reader menuitem if the default reader
   1.923 +          // isn't the selected application
   1.924 +          if (this._defaultSystemReader) {
   1.925 +            var shouldHide =
   1.926 +              this._defaultSystemReader.path == this._selectedApp.path;
   1.927 +            codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";"
   1.928 +          }
   1.929 +          Cu.evalInSandbox(codeStr, this._contentSandbox);
   1.930 +          break;
   1.931 +        }
   1.932 +      }
   1.933 +      case "bookmarks":
   1.934 +      default: {
   1.935 +        var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
   1.936 +        if (liveBookmarksMenuItem)
   1.937 +          this._safeDoCommand(liveBookmarksMenuItem);
   1.938 +      } 
   1.939 +    }
   1.940 +  },
   1.941 +
   1.942 +  _initSubscriptionUI: function FW__initSubscriptionUI() {
   1.943 +    var handlersMenuPopup = this._getUIElement("handlersMenuPopup");
   1.944 +    if (!handlersMenuPopup)
   1.945 +      return;
   1.946 + 
   1.947 +    var feedType = this._getFeedType();
   1.948 +    var codeStr;
   1.949 +
   1.950 +    // change the background
   1.951 +    var header = this._document.getElementById("feedHeader");
   1.952 +    this._contentSandbox.header = header;
   1.953 +    switch (feedType) {
   1.954 +      case Ci.nsIFeed.TYPE_VIDEO:
   1.955 +        codeStr = "header.className = 'videoPodcastBackground'; ";
   1.956 +        break;
   1.957 +
   1.958 +      case Ci.nsIFeed.TYPE_AUDIO:
   1.959 +        codeStr = "header.className = 'audioPodcastBackground'; ";
   1.960 +        break;
   1.961 +
   1.962 +      default:
   1.963 +        codeStr = "header.className = 'feedBackground'; ";
   1.964 +    }
   1.965 +
   1.966 +    var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
   1.967 +
   1.968 +    // Last-selected application
   1.969 +    var menuItem = liveBookmarksMenuItem.cloneNode(false);
   1.970 +    menuItem.removeAttribute("selected");
   1.971 +    menuItem.setAttribute("anonid", "selectedAppMenuItem");
   1.972 +    menuItem.className = "menuitem-iconic selectedAppMenuItem";
   1.973 +    menuItem.setAttribute("handlerType", "client");
   1.974 +    try {
   1.975 +      var prefs = Cc["@mozilla.org/preferences-service;1"].
   1.976 +                  getService(Ci.nsIPrefBranch);
   1.977 +      this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
   1.978 +                                                Ci.nsILocalFile);
   1.979 +
   1.980 +      if (this._selectedApp.exists())
   1.981 +        this._initMenuItemWithFile(menuItem, this._selectedApp);
   1.982 +      else {
   1.983 +        // Hide the menuitem if the last selected application doesn't exist
   1.984 +        menuItem.setAttribute("hidden", true);
   1.985 +      }
   1.986 +    }
   1.987 +    catch(ex) {
   1.988 +      // Hide the menuitem until an application is selected
   1.989 +      menuItem.setAttribute("hidden", true);
   1.990 +    }
   1.991 +    this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
   1.992 +    this._contentSandbox.selectedAppMenuItem = menuItem;
   1.993 +    
   1.994 +    codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
   1.995 +
   1.996 +    // List the default feed reader
   1.997 +    try {
   1.998 +      this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"].
   1.999 +                                  getService(Ci.nsIShellService).
  1.1000 +                                  defaultFeedReader;
  1.1001 +      menuItem = liveBookmarksMenuItem.cloneNode(false);
  1.1002 +      menuItem.removeAttribute("selected");
  1.1003 +      menuItem.setAttribute("anonid", "defaultHandlerMenuItem");
  1.1004 +      menuItem.className = "menuitem-iconic defaultHandlerMenuItem";
  1.1005 +      menuItem.setAttribute("handlerType", "client");
  1.1006 +
  1.1007 +      this._initMenuItemWithFile(menuItem, this._defaultSystemReader);
  1.1008 +
  1.1009 +      // Hide the default reader item if it points to the same application
  1.1010 +      // as the last-selected application
  1.1011 +      if (this._selectedApp &&
  1.1012 +          this._selectedApp.path == this._defaultSystemReader.path)
  1.1013 +        menuItem.hidden = true;
  1.1014 +    }
  1.1015 +    catch(ex) { menuItem = null; /* no default reader */ }
  1.1016 +
  1.1017 +    if (menuItem) {
  1.1018 +      this._contentSandbox.defaultHandlerMenuItem = menuItem;
  1.1019 +      codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
  1.1020 +    }
  1.1021 +
  1.1022 +    // "Choose Application..." menuitem
  1.1023 +    menuItem = liveBookmarksMenuItem.cloneNode(false);
  1.1024 +    menuItem.removeAttribute("selected");
  1.1025 +    menuItem.setAttribute("anonid", "chooseApplicationMenuItem");
  1.1026 +    menuItem.className = "menuitem-iconic chooseApplicationMenuItem";
  1.1027 +    menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
  1.1028 +
  1.1029 +    this._contentSandbox.chooseAppMenuItem = menuItem;
  1.1030 +    codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
  1.1031 +
  1.1032 +    // separator
  1.1033 +    this._contentSandbox.chooseAppSep =
  1.1034 +      menuItem = liveBookmarksMenuItem.nextSibling.cloneNode(false);
  1.1035 +    codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
  1.1036 +
  1.1037 +    Cu.evalInSandbox(codeStr, this._contentSandbox);
  1.1038 +
  1.1039 +    // List of web handlers
  1.1040 +    var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  1.1041 +               getService(Ci.nsIWebContentConverterService);
  1.1042 +    var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
  1.1043 +    if (handlers.length != 0) {
  1.1044 +      for (var i = 0; i < handlers.length; ++i) {
  1.1045 +        menuItem = liveBookmarksMenuItem.cloneNode(false);
  1.1046 +        menuItem.removeAttribute("selected");
  1.1047 +        menuItem.className = "menuitem-iconic";
  1.1048 +        menuItem.setAttribute("label", handlers[i].name);
  1.1049 +        menuItem.setAttribute("handlerType", "web");
  1.1050 +        menuItem.setAttribute("webhandlerurl", handlers[i].uri);
  1.1051 +        this._contentSandbox.menuItem = menuItem;
  1.1052 +        codeStr = "handlersMenuPopup.appendChild(menuItem);";
  1.1053 +        Cu.evalInSandbox(codeStr, this._contentSandbox);
  1.1054 +
  1.1055 +        this._setFaviconForWebReader(handlers[i].uri, menuItem);
  1.1056 +      }
  1.1057 +      this._contentSandbox.menuItem = null;
  1.1058 +    }
  1.1059 +
  1.1060 +    this._setSelectedHandler(feedType);
  1.1061 +
  1.1062 +    // "Subscribe using..."
  1.1063 +    this._setSubscribeUsingLabel();
  1.1064 +
  1.1065 +    // "Always use..." checkbox initial state
  1.1066 +    this._setAlwaysUseCheckedState(feedType);
  1.1067 +    this._setAlwaysUseLabel();
  1.1068 +
  1.1069 +    // We update the "Always use.." checkbox label whenever the selected item
  1.1070 +    // in the list is changed
  1.1071 +    handlersMenuPopup.addEventListener("command", this, false);
  1.1072 +
  1.1073 +    // Set up the "Subscribe Now" button
  1.1074 +    this._getUIElement("subscribeButton")
  1.1075 +        .addEventListener("command", this, false);
  1.1076 +
  1.1077 +    // first-run ui
  1.1078 +    var showFirstRunUI = true;
  1.1079 +    try {
  1.1080 +      showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI);
  1.1081 +    }
  1.1082 +    catch (ex) { }
  1.1083 +    if (showFirstRunUI) {
  1.1084 +      var textfeedinfo1, textfeedinfo2;
  1.1085 +      switch (feedType) {
  1.1086 +        case Ci.nsIFeed.TYPE_VIDEO:
  1.1087 +          textfeedinfo1 = "feedSubscriptionVideoPodcast1";
  1.1088 +          textfeedinfo2 = "feedSubscriptionVideoPodcast2";
  1.1089 +          break;
  1.1090 +        case Ci.nsIFeed.TYPE_AUDIO:
  1.1091 +          textfeedinfo1 = "feedSubscriptionAudioPodcast1";
  1.1092 +          textfeedinfo2 = "feedSubscriptionAudioPodcast2";
  1.1093 +          break;
  1.1094 +        default:
  1.1095 +          textfeedinfo1 = "feedSubscriptionFeed1";
  1.1096 +          textfeedinfo2 = "feedSubscriptionFeed2";
  1.1097 +      }
  1.1098 +
  1.1099 +      this._contentSandbox.feedinfo1 =
  1.1100 +        this._document.getElementById("feedSubscriptionInfo1");
  1.1101 +      this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
  1.1102 +      this._contentSandbox.feedinfo2 =
  1.1103 +        this._document.getElementById("feedSubscriptionInfo2");
  1.1104 +      this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
  1.1105 +      this._contentSandbox.header = header;
  1.1106 +      codeStr = "feedinfo1.textContent = feedinfo1Str; " +
  1.1107 +                "feedinfo2.textContent = feedinfo2Str; " +
  1.1108 +                "header.setAttribute('firstrun', 'true');"
  1.1109 +      Cu.evalInSandbox(codeStr, this._contentSandbox);
  1.1110 +      prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
  1.1111 +    }
  1.1112 +  },
  1.1113 +
  1.1114 +  /**
  1.1115 +   * Returns the original URI object of the feed and ensures that this
  1.1116 +   * component is only ever invoked from the preview document.  
  1.1117 +   * @param aWindow 
  1.1118 +   *        The window of the document invoking the BrowserFeedWriter
  1.1119 +   */
  1.1120 +  _getOriginalURI: function FW__getOriginalURI(aWindow) {
  1.1121 +    var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
  1.1122 +               getInterface(Ci.nsIWebNavigation).
  1.1123 +               QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
  1.1124 +
  1.1125 +    var resolvedURI = Cc["@mozilla.org/network/io-service;1"].
  1.1126 +                      getService(Ci.nsIIOService).
  1.1127 +                      newChannel("about:feeds", null, null).URI;
  1.1128 +
  1.1129 +    if (resolvedURI.equals(chan.URI))
  1.1130 +      return chan.originalURI;
  1.1131 +
  1.1132 +    return null;
  1.1133 +  },
  1.1134 +
  1.1135 +  _window: null,
  1.1136 +  _document: null,
  1.1137 +  _feedURI: null,
  1.1138 +  _feedPrincipal: null,
  1.1139 +  _handlersMenuList: null,
  1.1140 +
  1.1141 +  // BrowserFeedWriter WebIDL methods
  1.1142 +  init: function FW_init(aWindow) {
  1.1143 +    var window = aWindow;
  1.1144 +    this._feedURI = this._getOriginalURI(window);
  1.1145 +    if (!this._feedURI)
  1.1146 +      return;
  1.1147 +
  1.1148 +    this._window = window;
  1.1149 +    this._document = window.document;
  1.1150 +    this._document.getElementById("feedSubscribeLine").offsetTop;
  1.1151 +    this._handlersMenuList = this._getUIElement("handlersMenuList");
  1.1152 +
  1.1153 +    var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1.1154 +                 getService(Ci.nsIScriptSecurityManager);
  1.1155 +    this._feedPrincipal = secman.getSimpleCodebasePrincipal(this._feedURI);
  1.1156 +
  1.1157 +    LOG("Subscribe Preview: feed uri = " + this._window.location.href);
  1.1158 +
  1.1159 +    // Set up the subscription UI
  1.1160 +    this._initSubscriptionUI();
  1.1161 +    var prefs = Cc["@mozilla.org/preferences-service;1"].
  1.1162 +                getService(Ci.nsIPrefBranch);
  1.1163 +    prefs.addObserver(PREF_SELECTED_ACTION, this, false);
  1.1164 +    prefs.addObserver(PREF_SELECTED_READER, this, false);
  1.1165 +    prefs.addObserver(PREF_SELECTED_WEB, this, false);
  1.1166 +    prefs.addObserver(PREF_SELECTED_APP, this, false);
  1.1167 +    prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
  1.1168 +    prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
  1.1169 +    prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
  1.1170 +    prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
  1.1171 +
  1.1172 +    prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
  1.1173 +    prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
  1.1174 +    prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
  1.1175 +    prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
  1.1176 +  },
  1.1177 +
  1.1178 +  writeContent: function FW_writeContent() {
  1.1179 +    if (!this._window)
  1.1180 +      return;
  1.1181 +
  1.1182 +    try {
  1.1183 +      // Set up the feed content
  1.1184 +      var container = this._getContainer();
  1.1185 +      if (!container)
  1.1186 +        return;
  1.1187 +
  1.1188 +      this._setTitleText(container);
  1.1189 +      this._setTitleImage(container);
  1.1190 +      this._writeFeedContent(container);
  1.1191 +    }
  1.1192 +    finally {
  1.1193 +      this._removeFeedFromCache();
  1.1194 +    }
  1.1195 +  },
  1.1196 +
  1.1197 +  close: function FW_close() {
  1.1198 +    this._getUIElement("handlersMenuPopup")
  1.1199 +        .removeEventListener("command", this, false);
  1.1200 +    this._getUIElement("subscribeButton")
  1.1201 +        .removeEventListener("command", this, false);
  1.1202 +    this._document = null;
  1.1203 +    this._window = null;
  1.1204 +    var prefs = Cc["@mozilla.org/preferences-service;1"].
  1.1205 +                getService(Ci.nsIPrefBranch);
  1.1206 +    prefs.removeObserver(PREF_SELECTED_ACTION, this);
  1.1207 +    prefs.removeObserver(PREF_SELECTED_READER, this);
  1.1208 +    prefs.removeObserver(PREF_SELECTED_WEB, this);
  1.1209 +    prefs.removeObserver(PREF_SELECTED_APP, this);
  1.1210 +    prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
  1.1211 +    prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
  1.1212 +    prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
  1.1213 +    prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
  1.1214 +
  1.1215 +    prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
  1.1216 +    prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
  1.1217 +    prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
  1.1218 +    prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
  1.1219 +
  1.1220 +    this._removeFeedFromCache();
  1.1221 +    this.__faviconService = null;
  1.1222 +    this.__bundle = null;
  1.1223 +    this._feedURI = null;
  1.1224 +    this.__contentSandbox = null;
  1.1225 +  },
  1.1226 +
  1.1227 +  _removeFeedFromCache: function FW__removeFeedFromCache() {
  1.1228 +    if (this._feedURI) {
  1.1229 +      var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1.1230 +                        getService(Ci.nsIFeedResultService);
  1.1231 +      feedService.removeFeedResult(this._feedURI);
  1.1232 +      this._feedURI = null;
  1.1233 +    }
  1.1234 +  },
  1.1235 +
  1.1236 +  subscribe: function FW_subscribe() {
  1.1237 +    var feedType = this._getFeedType();
  1.1238 +
  1.1239 +    // Subscribe to the feed using the selected handler and save prefs
  1.1240 +    var prefs = Cc["@mozilla.org/preferences-service;1"].
  1.1241 +                getService(Ci.nsIPrefBranch);
  1.1242 +    var defaultHandler = "reader";
  1.1243 +    var useAsDefault = this._getUIElement("alwaysUse").getAttribute("checked");
  1.1244 +
  1.1245 +    var selectedItem = this._getSelectedItemFromMenulist(this._handlersMenuList);
  1.1246 +    let subscribeCallback = function() {
  1.1247 +      if (selectedItem.hasAttribute("webhandlerurl")) {
  1.1248 +        var webURI = selectedItem.getAttribute("webhandlerurl");
  1.1249 +        prefs.setCharPref(getPrefReaderForType(feedType), "web");
  1.1250 +
  1.1251 +        var supportsString = Cc["@mozilla.org/supports-string;1"].
  1.1252 +                             createInstance(Ci.nsISupportsString);
  1.1253 +        supportsString.data = webURI;
  1.1254 +        prefs.setComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString,
  1.1255 +                              supportsString);
  1.1256 +
  1.1257 +        var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
  1.1258 +                   getService(Ci.nsIWebContentConverterService);
  1.1259 +        var handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
  1.1260 +        if (handler) {
  1.1261 +          if (useAsDefault) {
  1.1262 +            wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
  1.1263 +          }
  1.1264 +
  1.1265 +          this._window.location.href = handler.getHandlerURI(this._window.location.href);
  1.1266 +        }
  1.1267 +      } else {
  1.1268 +        switch (selectedItem.getAttribute("anonid")) {
  1.1269 +          case "selectedAppMenuItem":
  1.1270 +            prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1.1271 +                                  this._selectedApp);
  1.1272 +            prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1.1273 +            break;
  1.1274 +          case "defaultHandlerMenuItem":
  1.1275 +            prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile, 
  1.1276 +                                  this._defaultSystemReader);
  1.1277 +            prefs.setCharPref(getPrefReaderForType(feedType), "client");
  1.1278 +            break;
  1.1279 +          case "liveBookmarksMenuItem":
  1.1280 +            defaultHandler = "bookmarks";
  1.1281 +            prefs.setCharPref(getPrefReaderForType(feedType), "bookmarks");
  1.1282 +            break;
  1.1283 +        }
  1.1284 +        var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
  1.1285 +                          getService(Ci.nsIFeedResultService);
  1.1286 +
  1.1287 +        // Pull the title and subtitle out of the document
  1.1288 +        var feedTitle = this._document.getElementById(TITLE_ID).textContent;
  1.1289 +        var feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
  1.1290 +        feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType);
  1.1291 +      }
  1.1292 +
  1.1293 +      // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
  1.1294 +      // to either "reader" (If a web reader or if an application is selected),
  1.1295 +      // or to "bookmarks" (if the live bookmarks option is selected).
  1.1296 +      // Otherwise, we should set it to "ask"
  1.1297 +      if (useAsDefault) {
  1.1298 +        prefs.setCharPref(getPrefActionForType(feedType), defaultHandler);
  1.1299 +      } else {
  1.1300 +        prefs.setCharPref(getPrefActionForType(feedType), "ask");
  1.1301 +      }
  1.1302 +    }.bind(this);
  1.1303 +
  1.1304 +    // Show the file picker before subscribing if the
  1.1305 +    // choose application menuitem was chosen using the keyboard
  1.1306 +    if (selectedItem.getAttribute("anonid") == "chooseApplicationMenuItem") {
  1.1307 +      this._chooseClientApp(function(aResult) {
  1.1308 +        if (aResult) {
  1.1309 +          selectedItem =
  1.1310 +            this._getSelectedItemFromMenulist(this._handlersMenuList);
  1.1311 +          subscribeCallback();
  1.1312 +        }
  1.1313 +      }.bind(this));
  1.1314 +    } else {
  1.1315 +      subscribeCallback();
  1.1316 +    }
  1.1317 +  },
  1.1318 +
  1.1319 +  // nsIObserver
  1.1320 +  observe: function FW_observe(subject, topic, data) {
  1.1321 +    if (!this._window) {
  1.1322 +      // this._window is null unless this.init was called with a trusted
  1.1323 +      // window object.
  1.1324 +      return;
  1.1325 +    }
  1.1326 +
  1.1327 +    var feedType = this._getFeedType();
  1.1328 +
  1.1329 +    if (topic == "nsPref:changed") {
  1.1330 +      switch (data) {
  1.1331 +        case PREF_SELECTED_READER:
  1.1332 +        case PREF_SELECTED_WEB:
  1.1333 +        case PREF_SELECTED_APP:
  1.1334 +        case PREF_VIDEO_SELECTED_READER:
  1.1335 +        case PREF_VIDEO_SELECTED_WEB:
  1.1336 +        case PREF_VIDEO_SELECTED_APP:
  1.1337 +        case PREF_AUDIO_SELECTED_READER:
  1.1338 +        case PREF_AUDIO_SELECTED_WEB:
  1.1339 +        case PREF_AUDIO_SELECTED_APP:
  1.1340 +          this._setSelectedHandler(feedType);
  1.1341 +          break;
  1.1342 +        case PREF_SELECTED_ACTION:
  1.1343 +        case PREF_VIDEO_SELECTED_ACTION:
  1.1344 +        case PREF_AUDIO_SELECTED_ACTION:
  1.1345 +          this._setAlwaysUseCheckedState(feedType);
  1.1346 +      }
  1.1347 +    } 
  1.1348 +  },
  1.1349 +
  1.1350 +  /**
  1.1351 +   * Sets the icon for the given web-reader item in the readers menu.
  1.1352 +   * The icon is fetched and stored through the favicon service.
  1.1353 +   *
  1.1354 +   * @param aReaderUrl
  1.1355 +   *        the reader url.
  1.1356 +   * @param aMenuItem
  1.1357 +   *        the reader item in the readers menulist.
  1.1358 +   *
  1.1359 +   * @note For privacy reasons we cannot set the image attribute directly
  1.1360 +   *       to the icon url.  See Bug 358878 for details.
  1.1361 +   */
  1.1362 +  _setFaviconForWebReader:
  1.1363 +  function FW__setFaviconForWebReader(aReaderUrl, aMenuItem) {
  1.1364 +    var readerURI = makeURI(aReaderUrl);
  1.1365 +    if (!/^https?$/.test(readerURI.scheme)) {
  1.1366 +      // Don't try to get a favicon for non http(s) URIs.
  1.1367 +      return;
  1.1368 +    }
  1.1369 +    var faviconURI = makeURI(readerURI.prePath + "/favicon.ico");
  1.1370 +    var self = this;
  1.1371 +    var usePrivateBrowsing = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
  1.1372 +                                         .getInterface(Ci.nsIWebNavigation)
  1.1373 +                                         .QueryInterface(Ci.nsIDocShell)
  1.1374 +                                         .QueryInterface(Ci.nsILoadContext)
  1.1375 +                                         .usePrivateBrowsing;
  1.1376 +    this._faviconService.setAndFetchFaviconForPage(readerURI, faviconURI, false,
  1.1377 +      usePrivateBrowsing ? this._faviconService.FAVICON_LOAD_PRIVATE
  1.1378 +                         : this._faviconService.FAVICON_LOAD_NON_PRIVATE,
  1.1379 +      function (aURI, aDataLen, aData, aMimeType) {
  1.1380 +        if (aDataLen > 0) {
  1.1381 +          var dataURL = "data:" + aMimeType + ";base64," +
  1.1382 +                        btoa(String.fromCharCode.apply(null, aData));
  1.1383 +          self._contentSandbox.menuItem = aMenuItem;
  1.1384 +          self._contentSandbox.dataURL = dataURL;
  1.1385 +          var codeStr = "menuItem.setAttribute('image', dataURL);";
  1.1386 +          Cu.evalInSandbox(codeStr, self._contentSandbox);
  1.1387 +          self._contentSandbox.menuItem = null;
  1.1388 +          self._contentSandbox.dataURL = null;
  1.1389 +        }
  1.1390 +      });
  1.1391 +  },
  1.1392 +
  1.1393 +  classID: FEEDWRITER_CID,
  1.1394 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver,
  1.1395 +                                         Ci.nsINavHistoryObserver,
  1.1396 +                                         Ci.nsIDOMGlobalPropertyInitializer])
  1.1397 +};
  1.1398 +
  1.1399 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FeedWriter]);

mercurial