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]);