FeedWriter.js 48 KB

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