browser/components/feeds/src/FeedWriter.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial