|
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 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"); |
|
9 |
|
10 const Cc = Components.classes; |
|
11 const Ci = Components.interfaces; |
|
12 const Cr = Components.results; |
|
13 |
|
14 function LOG(str) { |
|
15 dump("*** " + str + "\n"); |
|
16 } |
|
17 |
|
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"; |
|
21 |
|
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 = "*/*"; |
|
26 |
|
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"; |
|
31 |
|
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"; |
|
36 |
|
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"; |
|
41 |
|
42 function getPrefAppForType(t) { |
|
43 switch (t) { |
|
44 case Ci.nsIFeed.TYPE_VIDEO: |
|
45 return PREF_VIDEO_SELECTED_APP; |
|
46 |
|
47 case Ci.nsIFeed.TYPE_AUDIO: |
|
48 return PREF_AUDIO_SELECTED_APP; |
|
49 |
|
50 default: |
|
51 return PREF_SELECTED_APP; |
|
52 } |
|
53 } |
|
54 |
|
55 function getPrefWebForType(t) { |
|
56 switch (t) { |
|
57 case Ci.nsIFeed.TYPE_VIDEO: |
|
58 return PREF_VIDEO_SELECTED_WEB; |
|
59 |
|
60 case Ci.nsIFeed.TYPE_AUDIO: |
|
61 return PREF_AUDIO_SELECTED_WEB; |
|
62 |
|
63 default: |
|
64 return PREF_SELECTED_WEB; |
|
65 } |
|
66 } |
|
67 |
|
68 function getPrefActionForType(t) { |
|
69 switch (t) { |
|
70 case Ci.nsIFeed.TYPE_VIDEO: |
|
71 return PREF_VIDEO_SELECTED_ACTION; |
|
72 |
|
73 case Ci.nsIFeed.TYPE_AUDIO: |
|
74 return PREF_AUDIO_SELECTED_ACTION; |
|
75 |
|
76 default: |
|
77 return PREF_SELECTED_ACTION; |
|
78 } |
|
79 } |
|
80 |
|
81 function getPrefReaderForType(t) { |
|
82 switch (t) { |
|
83 case Ci.nsIFeed.TYPE_VIDEO: |
|
84 return PREF_VIDEO_SELECTED_READER; |
|
85 |
|
86 case Ci.nsIFeed.TYPE_AUDIO: |
|
87 return PREF_AUDIO_SELECTED_READER; |
|
88 |
|
89 default: |
|
90 return PREF_SELECTED_READER; |
|
91 } |
|
92 } |
|
93 |
|
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 } |
|
105 |
|
106 function FeedConverter() { |
|
107 } |
|
108 FeedConverter.prototype = { |
|
109 classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"), |
|
110 |
|
111 /** |
|
112 * This is the downloaded text data for the feed. |
|
113 */ |
|
114 _data: null, |
|
115 |
|
116 /** |
|
117 * This is the object listening to the conversion, which is ultimately the |
|
118 * docshell for the load. |
|
119 */ |
|
120 _listener: null, |
|
121 |
|
122 /** |
|
123 * Records if the feed was sniffed |
|
124 */ |
|
125 _sniffed: false, |
|
126 |
|
127 /** |
|
128 * See nsIStreamConverter.idl |
|
129 */ |
|
130 convert: function FC_convert(sourceStream, sourceType, destinationType, |
|
131 context) { |
|
132 throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
|
133 }, |
|
134 |
|
135 /** |
|
136 * See nsIStreamConverter.idl |
|
137 */ |
|
138 asyncConvertData: function FC_asyncConvertData(sourceType, destinationType, |
|
139 listener, context) { |
|
140 this._listener = listener; |
|
141 }, |
|
142 |
|
143 /** |
|
144 * Whether or not the preview page is being forced. |
|
145 */ |
|
146 _forcePreviewPage: false, |
|
147 |
|
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 }, |
|
156 |
|
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"); |
|
198 |
|
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; |
|
217 |
|
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 } |
|
232 |
|
233 var ios = |
|
234 Cc["@mozilla.org/network/io-service;1"]. |
|
235 getService(Ci.nsIIOService); |
|
236 var chromeChannel; |
|
237 |
|
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) { |
|
242 |
|
243 // Store the result in the result service so that the display |
|
244 // page can access it. |
|
245 feedService.addFeedResult(result); |
|
246 |
|
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 } |
|
256 |
|
257 chromeChannel.loadGroup = this._request.loadGroup; |
|
258 chromeChannel.asyncOpen(this._listener, null); |
|
259 } |
|
260 finally { |
|
261 this._releaseHandles(); |
|
262 } |
|
263 }, |
|
264 |
|
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 }, |
|
274 |
|
275 /** |
|
276 * See nsIRequestObserver.idl |
|
277 */ |
|
278 onStartRequest: function FC_onStartRequest(request, context) { |
|
279 var channel = request.QueryInterface(Ci.nsIChannel); |
|
280 |
|
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 } |
|
297 |
|
298 this._request = request; |
|
299 |
|
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; |
|
307 |
|
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); |
|
314 |
|
315 this._processor.onStartRequest(request, context); |
|
316 }, |
|
317 |
|
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 }, |
|
325 |
|
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 }; |
|
339 |
|
340 /** |
|
341 * Keeps parsed FeedResults around for use elsewhere in the UI after the stream |
|
342 * converter completes. |
|
343 */ |
|
344 function FeedResultService() { |
|
345 } |
|
346 |
|
347 FeedResultService.prototype = { |
|
348 classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"), |
|
349 |
|
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: { }, |
|
355 |
|
356 /** |
|
357 * See nsIFeedResultService.idl |
|
358 */ |
|
359 forcePreviewPage: false, |
|
360 |
|
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); |
|
368 |
|
369 var handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks"); |
|
370 if (handler == "ask" || handler == "reader") |
|
371 handler = safeGetCharPref(getPrefReaderForType(feedType), "bookmarks"); |
|
372 |
|
373 switch (handler) { |
|
374 case "client": |
|
375 var clientApp = prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile); |
|
376 |
|
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; |
|
394 |
|
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; |
|
412 |
|
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 }, |
|
426 |
|
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 }, |
|
438 |
|
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 }, |
|
451 |
|
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 } |
|
467 |
|
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 }, |
|
475 |
|
476 createInstance: function FRS_createInstance(outer, iid) { |
|
477 if (outer != null) |
|
478 throw Cr.NS_ERROR_NO_AGGREGATION; |
|
479 return this.QueryInterface(iid); |
|
480 }, |
|
481 |
|
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 }; |
|
490 |
|
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 }, |
|
505 |
|
506 get scheme() { |
|
507 return this._scheme; |
|
508 }, |
|
509 |
|
510 get protocolFlags() { |
|
511 return this._http.protocolFlags; |
|
512 }, |
|
513 |
|
514 get defaultPort() { |
|
515 return this._http.defaultPort; |
|
516 }, |
|
517 |
|
518 allowPort: function GPH_allowPort(port, scheme) { |
|
519 return this._http.allowPort(port, scheme); |
|
520 }, |
|
521 |
|
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. |
|
526 |
|
527 var scheme = this._scheme + ":"; |
|
528 if (spec.substr(0, scheme.length) != scheme) |
|
529 throw Cr.NS_ERROR_MALFORMED_URI; |
|
530 |
|
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; |
|
540 |
|
541 var uri = netutil.newSimpleNestedURI(inner); |
|
542 uri.spec = inner.spec.replace(prefix, scheme); |
|
543 return uri; |
|
544 }, |
|
545 |
|
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 }, |
|
556 |
|
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 }; |
|
564 |
|
565 function FeedProtocolHandler() { |
|
566 this._init('feed'); |
|
567 } |
|
568 FeedProtocolHandler.prototype = new GenericProtocolHandler(); |
|
569 FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}"); |
|
570 |
|
571 function PodCastProtocolHandler() { |
|
572 this._init('pcast'); |
|
573 } |
|
574 PodCastProtocolHandler.prototype = new GenericProtocolHandler(); |
|
575 PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}"); |
|
576 |
|
577 var components = [FeedConverter, |
|
578 FeedResultService, |
|
579 FeedProtocolHandler, |
|
580 PodCastProtocolHandler]; |
|
581 |
|
582 |
|
583 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); |