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