Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
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/. */
6 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
7 Components.utils.import("resource://gre/modules/debug.js");
8 Components.utils.import("resource://gre/modules/Services.jsm");
10 const Cc = Components.classes;
11 const Ci = Components.interfaces;
12 const Cr = Components.results;
14 function LOG(str) {
15 dump("*** " + str + "\n");
16 }
18 const FS_CONTRACTID = "@mozilla.org/browser/feeds/result-service;1";
19 const FPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=feed";
20 const PCPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=pcast";
22 const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
23 const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
24 const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
25 const TYPE_ANY = "*/*";
27 const PREF_SELECTED_APP = "browser.feeds.handlers.application";
28 const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
29 const PREF_SELECTED_ACTION = "browser.feeds.handler";
30 const PREF_SELECTED_READER = "browser.feeds.handler.default";
32 const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
33 const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
34 const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
35 const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
37 const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
38 const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
39 const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
40 const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
42 function getPrefAppForType(t) {
43 switch (t) {
44 case Ci.nsIFeed.TYPE_VIDEO:
45 return PREF_VIDEO_SELECTED_APP;
47 case Ci.nsIFeed.TYPE_AUDIO:
48 return PREF_AUDIO_SELECTED_APP;
50 default:
51 return PREF_SELECTED_APP;
52 }
53 }
55 function getPrefWebForType(t) {
56 switch (t) {
57 case Ci.nsIFeed.TYPE_VIDEO:
58 return PREF_VIDEO_SELECTED_WEB;
60 case Ci.nsIFeed.TYPE_AUDIO:
61 return PREF_AUDIO_SELECTED_WEB;
63 default:
64 return PREF_SELECTED_WEB;
65 }
66 }
68 function getPrefActionForType(t) {
69 switch (t) {
70 case Ci.nsIFeed.TYPE_VIDEO:
71 return PREF_VIDEO_SELECTED_ACTION;
73 case Ci.nsIFeed.TYPE_AUDIO:
74 return PREF_AUDIO_SELECTED_ACTION;
76 default:
77 return PREF_SELECTED_ACTION;
78 }
79 }
81 function getPrefReaderForType(t) {
82 switch (t) {
83 case Ci.nsIFeed.TYPE_VIDEO:
84 return PREF_VIDEO_SELECTED_READER;
86 case Ci.nsIFeed.TYPE_AUDIO:
87 return PREF_AUDIO_SELECTED_READER;
89 default:
90 return PREF_SELECTED_READER;
91 }
92 }
94 function safeGetCharPref(pref, defaultValue) {
95 var prefs =
96 Cc["@mozilla.org/preferences-service;1"].
97 getService(Ci.nsIPrefBranch);
98 try {
99 return prefs.getCharPref(pref);
100 }
101 catch (e) {
102 }
103 return defaultValue;
104 }
106 function FeedConverter() {
107 }
108 FeedConverter.prototype = {
109 classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"),
111 /**
112 * This is the downloaded text data for the feed.
113 */
114 _data: null,
116 /**
117 * This is the object listening to the conversion, which is ultimately the
118 * docshell for the load.
119 */
120 _listener: null,
122 /**
123 * Records if the feed was sniffed
124 */
125 _sniffed: false,
127 /**
128 * See nsIStreamConverter.idl
129 */
130 convert: function FC_convert(sourceStream, sourceType, destinationType,
131 context) {
132 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
133 },
135 /**
136 * See nsIStreamConverter.idl
137 */
138 asyncConvertData: function FC_asyncConvertData(sourceType, destinationType,
139 listener, context) {
140 this._listener = listener;
141 },
143 /**
144 * Whether or not the preview page is being forced.
145 */
146 _forcePreviewPage: false,
148 /**
149 * Release our references to various things once we're done using them.
150 */
151 _releaseHandles: function FC__releaseHandles() {
152 this._listener = null;
153 this._request = null;
154 this._processor = null;
155 },
157 /**
158 * See nsIFeedResultListener.idl
159 */
160 handleResult: function FC_handleResult(result) {
161 // Feeds come in various content types, which our feed sniffer coerces to
162 // the maybe.feed type. However, feeds are used as a transport for
163 // different data types, e.g. news/blogs (traditional feed), video/audio
164 // (podcasts) and photos (photocasts, photostreams). Each of these is
165 // different in that there's a different class of application suitable for
166 // handling feeds of that type, but without a content-type differentiation
167 // it is difficult for us to disambiguate.
168 //
169 // The other problem is that if the user specifies an auto-action handler
170 // for one feed application, the fact that the content type is shared means
171 // that all other applications will auto-load with that handler too,
172 // regardless of the content-type.
173 //
174 // This means that content-type alone is not enough to determine whether
175 // or not a feed should be auto-handled. This means that for feeds we need
176 // to always use this stream converter, even when an auto-action is
177 // specified, not the basic one provided by WebContentConverter. This
178 // converter needs to consume all of the data and parse it, and based on
179 // that determination make a judgment about type.
180 //
181 // Since there are no content types for this content, and I'm not going to
182 // invent any, the upshot is that while a user can set an auto-handler for
183 // generic feed content, the system will prevent them from setting an auto-
184 // handler for other stream types. In those cases, the user will always see
185 // the preview page and have to select a handler. We can guess and show
186 // a client handler, but will not be able to show web handlers for those
187 // types.
188 //
189 // If this is just a feed, not some kind of specialized application, then
190 // auto-handlers can be set and we should obey them.
191 try {
192 var feedService =
193 Cc["@mozilla.org/browser/feeds/result-service;1"].
194 getService(Ci.nsIFeedResultService);
195 if (!this._forcePreviewPage && result.doc) {
196 var feed = result.doc.QueryInterface(Ci.nsIFeed);
197 var handler = safeGetCharPref(getPrefActionForType(feed.type), "ask");
199 if (handler != "ask") {
200 if (handler == "reader")
201 handler = safeGetCharPref(getPrefReaderForType(feed.type), "bookmarks");
202 switch (handler) {
203 case "web":
204 var wccr =
205 Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
206 getService(Ci.nsIWebContentConverterService);
207 if ((feed.type == Ci.nsIFeed.TYPE_FEED &&
208 wccr.getAutoHandler(TYPE_MAYBE_FEED)) ||
209 (feed.type == Ci.nsIFeed.TYPE_VIDEO &&
210 wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) ||
211 (feed.type == Ci.nsIFeed.TYPE_AUDIO &&
212 wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) {
213 wccr.loadPreferredHandler(this._request);
214 return;
215 }
216 break;
218 default:
219 LOG("unexpected handler: " + handler);
220 // fall through -- let feed service handle error
221 case "bookmarks":
222 case "client":
223 try {
224 var title = feed.title ? feed.title.plainText() : "";
225 var desc = feed.subtitle ? feed.subtitle.plainText() : "";
226 feedService.addToClientReader(result.uri.spec, title, desc, feed.type);
227 return;
228 } catch(ex) { /* fallback to preview mode */ }
229 }
230 }
231 }
233 var ios =
234 Cc["@mozilla.org/network/io-service;1"].
235 getService(Ci.nsIIOService);
236 var chromeChannel;
238 // If there was no automatic handler, or this was a podcast,
239 // photostream or some other kind of application, show the preview page
240 // if the parser returned a document.
241 if (result.doc) {
243 // Store the result in the result service so that the display
244 // page can access it.
245 feedService.addFeedResult(result);
247 // Now load the actual XUL document.
248 var aboutFeedsURI = ios.newURI("about:feeds", null, null);
249 chromeChannel = ios.newChannelFromURI(aboutFeedsURI, null);
250 chromeChannel.originalURI = result.uri;
251 chromeChannel.owner =
252 Services.scriptSecurityManager.getNoAppCodebasePrincipal(aboutFeedsURI);
253 } else {
254 chromeChannel = ios.newChannelFromURI(result.uri, null);
255 }
257 chromeChannel.loadGroup = this._request.loadGroup;
258 chromeChannel.asyncOpen(this._listener, null);
259 }
260 finally {
261 this._releaseHandles();
262 }
263 },
265 /**
266 * See nsIStreamListener.idl
267 */
268 onDataAvailable: function FC_onDataAvailable(request, context, inputStream,
269 sourceOffset, count) {
270 if (this._processor)
271 this._processor.onDataAvailable(request, context, inputStream,
272 sourceOffset, count);
273 },
275 /**
276 * See nsIRequestObserver.idl
277 */
278 onStartRequest: function FC_onStartRequest(request, context) {
279 var channel = request.QueryInterface(Ci.nsIChannel);
281 // Check for a header that tells us there was no sniffing
282 // The value doesn't matter.
283 try {
284 var httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
285 // Make sure to check requestSucceeded before the potentially-throwing
286 // getResponseHeader.
287 if (!httpChannel.requestSucceeded) {
288 // Just give up, but don't forget to cancel the channel first!
289 request.cancel(Cr.NS_BINDING_ABORTED);
290 return;
291 }
292 var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed");
293 }
294 catch (ex) {
295 this._sniffed = true;
296 }
298 this._request = request;
300 // Save and reset the forced state bit early, in case there's some kind of
301 // error.
302 var feedService =
303 Cc["@mozilla.org/browser/feeds/result-service;1"].
304 getService(Ci.nsIFeedResultService);
305 this._forcePreviewPage = feedService.forcePreviewPage;
306 feedService.forcePreviewPage = false;
308 // Parse feed data as it comes in
309 this._processor =
310 Cc["@mozilla.org/feed-processor;1"].
311 createInstance(Ci.nsIFeedProcessor);
312 this._processor.listener = this;
313 this._processor.parseAsync(null, channel.URI);
315 this._processor.onStartRequest(request, context);
316 },
318 /**
319 * See nsIRequestObserver.idl
320 */
321 onStopRequest: function FC_onStopRequest(request, context, status) {
322 if (this._processor)
323 this._processor.onStopRequest(request, context, status);
324 },
326 /**
327 * See nsISupports.idl
328 */
329 QueryInterface: function FC_QueryInterface(iid) {
330 if (iid.equals(Ci.nsIFeedResultListener) ||
331 iid.equals(Ci.nsIStreamConverter) ||
332 iid.equals(Ci.nsIStreamListener) ||
333 iid.equals(Ci.nsIRequestObserver)||
334 iid.equals(Ci.nsISupports))
335 return this;
336 throw Cr.NS_ERROR_NO_INTERFACE;
337 },
338 };
340 /**
341 * Keeps parsed FeedResults around for use elsewhere in the UI after the stream
342 * converter completes.
343 */
344 function FeedResultService() {
345 }
347 FeedResultService.prototype = {
348 classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"),
350 /**
351 * A URI spec -> [nsIFeedResult] hash. We have to keep a list as the
352 * value in case the same URI is requested concurrently.
353 */
354 _results: { },
356 /**
357 * See nsIFeedResultService.idl
358 */
359 forcePreviewPage: false,
361 /**
362 * See nsIFeedResultService.idl
363 */
364 addToClientReader: function FRS_addToClientReader(spec, title, subtitle, feedType) {
365 var prefs =
366 Cc["@mozilla.org/preferences-service;1"].
367 getService(Ci.nsIPrefBranch);
369 var handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
370 if (handler == "ask" || handler == "reader")
371 handler = safeGetCharPref(getPrefReaderForType(feedType), "bookmarks");
373 switch (handler) {
374 case "client":
375 var clientApp = prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
377 // For the benefit of applications that might know how to deal with more
378 // URLs than just feeds, send feed: URLs in the following format:
379 //
380 // http urls: replace scheme with feed, e.g.
381 // http://foo.com/index.rdf -> feed://foo.com/index.rdf
382 // other urls: prepend feed: scheme, e.g.
383 // https://foo.com/index.rdf -> feed:https://foo.com/index.rdf
384 var ios =
385 Cc["@mozilla.org/network/io-service;1"].
386 getService(Ci.nsIIOService);
387 var feedURI = ios.newURI(spec, null, null);
388 if (feedURI.schemeIs("http")) {
389 feedURI.scheme = "feed";
390 spec = feedURI.spec;
391 }
392 else
393 spec = "feed:" + spec;
395 // Retrieving the shell service might fail on some systems, most
396 // notably systems where GNOME is not installed.
397 try {
398 var ss =
399 Cc["@mozilla.org/browser/shell-service;1"].
400 getService(Ci.nsIShellService);
401 ss.openApplicationWithURI(clientApp, spec);
402 } catch(e) {
403 // If we couldn't use the shell service, fallback to using a
404 // nsIProcess instance
405 var p =
406 Cc["@mozilla.org/process/util;1"].
407 createInstance(Ci.nsIProcess);
408 p.init(clientApp);
409 p.run(false, [spec], 1);
410 }
411 break;
413 default:
414 // "web" should have been handled elsewhere
415 LOG("unexpected handler: " + handler);
416 // fall through
417 case "bookmarks":
418 var wm =
419 Cc["@mozilla.org/appshell/window-mediator;1"].
420 getService(Ci.nsIWindowMediator);
421 var topWindow = wm.getMostRecentWindow("navigator:browser");
422 topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle);
423 break;
424 }
425 },
427 /**
428 * See nsIFeedResultService.idl
429 */
430 addFeedResult: function FRS_addFeedResult(feedResult) {
431 NS_ASSERT(feedResult.uri != null, "null URI!");
432 NS_ASSERT(feedResult.uri != null, "null feedResult!");
433 var spec = feedResult.uri.spec;
434 if(!this._results[spec])
435 this._results[spec] = [];
436 this._results[spec].push(feedResult);
437 },
439 /**
440 * See nsIFeedResultService.idl
441 */
442 getFeedResult: function RFS_getFeedResult(uri) {
443 NS_ASSERT(uri != null, "null URI!");
444 var resultList = this._results[uri.spec];
445 for (var i in resultList) {
446 if (resultList[i].uri == uri)
447 return resultList[i];
448 }
449 return null;
450 },
452 /**
453 * See nsIFeedResultService.idl
454 */
455 removeFeedResult: function FRS_removeFeedResult(uri) {
456 NS_ASSERT(uri != null, "null URI!");
457 var resultList = this._results[uri.spec];
458 if (!resultList)
459 return;
460 var deletions = 0;
461 for (var i = 0; i < resultList.length; ++i) {
462 if (resultList[i].uri == uri) {
463 delete resultList[i];
464 ++deletions;
465 }
466 }
468 // send the holes to the end
469 resultList.sort();
470 // and trim the list
471 resultList.splice(resultList.length - deletions, deletions);
472 if (resultList.length == 0)
473 delete this._results[uri.spec];
474 },
476 createInstance: function FRS_createInstance(outer, iid) {
477 if (outer != null)
478 throw Cr.NS_ERROR_NO_AGGREGATION;
479 return this.QueryInterface(iid);
480 },
482 QueryInterface: function FRS_QueryInterface(iid) {
483 if (iid.equals(Ci.nsIFeedResultService) ||
484 iid.equals(Ci.nsIFactory) ||
485 iid.equals(Ci.nsISupports))
486 return this;
487 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
488 },
489 };
491 /**
492 * A protocol handler that attempts to deal with the variant forms of feed:
493 * URIs that are actually either http or https.
494 */
495 function GenericProtocolHandler() {
496 }
497 GenericProtocolHandler.prototype = {
498 _init: function GPH_init(scheme) {
499 var ios =
500 Cc["@mozilla.org/network/io-service;1"].
501 getService(Ci.nsIIOService);
502 this._http = ios.getProtocolHandler("http");
503 this._scheme = scheme;
504 },
506 get scheme() {
507 return this._scheme;
508 },
510 get protocolFlags() {
511 return this._http.protocolFlags;
512 },
514 get defaultPort() {
515 return this._http.defaultPort;
516 },
518 allowPort: function GPH_allowPort(port, scheme) {
519 return this._http.allowPort(port, scheme);
520 },
522 newURI: function GPH_newURI(spec, originalCharset, baseURI) {
523 // Feed URIs can be either nested URIs of the form feed:realURI (in which
524 // case we create a nested URI for the realURI) or feed://example.com, in
525 // which case we create a nested URI for the real protocol which is http.
527 var scheme = this._scheme + ":";
528 if (spec.substr(0, scheme.length) != scheme)
529 throw Cr.NS_ERROR_MALFORMED_URI;
531 var prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : "";
532 var inner = Cc["@mozilla.org/network/io-service;1"].
533 getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix),
534 originalCharset, baseURI);
535 var netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
536 const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler
537 .URI_INHERITS_SECURITY_CONTEXT;
538 if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT))
539 throw Cr.NS_ERROR_MALFORMED_URI;
541 var uri = netutil.newSimpleNestedURI(inner);
542 uri.spec = inner.spec.replace(prefix, scheme);
543 return uri;
544 },
546 newChannel: function GPH_newChannel(aUri) {
547 var inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
548 var channel = Cc["@mozilla.org/network/io-service;1"].
549 getService(Ci.nsIIOService).newChannelFromURI(inner, null);
550 if (channel instanceof Components.interfaces.nsIHttpChannel)
551 // Set this so we know this is supposed to be a feed
552 channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
553 channel.originalURI = aUri;
554 return channel;
555 },
557 QueryInterface: function GPH_QueryInterface(iid) {
558 if (iid.equals(Ci.nsIProtocolHandler) ||
559 iid.equals(Ci.nsISupports))
560 return this;
561 throw Cr.NS_ERROR_NO_INTERFACE;
562 }
563 };
565 function FeedProtocolHandler() {
566 this._init('feed');
567 }
568 FeedProtocolHandler.prototype = new GenericProtocolHandler();
569 FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}");
571 function PodCastProtocolHandler() {
572 this._init('pcast');
573 }
574 PodCastProtocolHandler.prototype = new GenericProtocolHandler();
575 PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}");
577 var components = [FeedConverter,
578 FeedResultService,
579 FeedProtocolHandler,
580 PodCastProtocolHandler];
583 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);