1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/components/feeds/src/FeedConverter.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,583 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.10 +Components.utils.import("resource://gre/modules/debug.js"); 1.11 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.12 + 1.13 +const Cc = Components.classes; 1.14 +const Ci = Components.interfaces; 1.15 +const Cr = Components.results; 1.16 + 1.17 +function LOG(str) { 1.18 + dump("*** " + str + "\n"); 1.19 +} 1.20 + 1.21 +const FS_CONTRACTID = "@mozilla.org/browser/feeds/result-service;1"; 1.22 +const FPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=feed"; 1.23 +const PCPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=pcast"; 1.24 + 1.25 +const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed"; 1.26 +const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed"; 1.27 +const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed"; 1.28 +const TYPE_ANY = "*/*"; 1.29 + 1.30 +const PREF_SELECTED_APP = "browser.feeds.handlers.application"; 1.31 +const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice"; 1.32 +const PREF_SELECTED_ACTION = "browser.feeds.handler"; 1.33 +const PREF_SELECTED_READER = "browser.feeds.handler.default"; 1.34 + 1.35 +const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application"; 1.36 +const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice"; 1.37 +const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler"; 1.38 +const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default"; 1.39 + 1.40 +const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application"; 1.41 +const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice"; 1.42 +const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler"; 1.43 +const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default"; 1.44 + 1.45 +function getPrefAppForType(t) { 1.46 + switch (t) { 1.47 + case Ci.nsIFeed.TYPE_VIDEO: 1.48 + return PREF_VIDEO_SELECTED_APP; 1.49 + 1.50 + case Ci.nsIFeed.TYPE_AUDIO: 1.51 + return PREF_AUDIO_SELECTED_APP; 1.52 + 1.53 + default: 1.54 + return PREF_SELECTED_APP; 1.55 + } 1.56 +} 1.57 + 1.58 +function getPrefWebForType(t) { 1.59 + switch (t) { 1.60 + case Ci.nsIFeed.TYPE_VIDEO: 1.61 + return PREF_VIDEO_SELECTED_WEB; 1.62 + 1.63 + case Ci.nsIFeed.TYPE_AUDIO: 1.64 + return PREF_AUDIO_SELECTED_WEB; 1.65 + 1.66 + default: 1.67 + return PREF_SELECTED_WEB; 1.68 + } 1.69 +} 1.70 + 1.71 +function getPrefActionForType(t) { 1.72 + switch (t) { 1.73 + case Ci.nsIFeed.TYPE_VIDEO: 1.74 + return PREF_VIDEO_SELECTED_ACTION; 1.75 + 1.76 + case Ci.nsIFeed.TYPE_AUDIO: 1.77 + return PREF_AUDIO_SELECTED_ACTION; 1.78 + 1.79 + default: 1.80 + return PREF_SELECTED_ACTION; 1.81 + } 1.82 +} 1.83 + 1.84 +function getPrefReaderForType(t) { 1.85 + switch (t) { 1.86 + case Ci.nsIFeed.TYPE_VIDEO: 1.87 + return PREF_VIDEO_SELECTED_READER; 1.88 + 1.89 + case Ci.nsIFeed.TYPE_AUDIO: 1.90 + return PREF_AUDIO_SELECTED_READER; 1.91 + 1.92 + default: 1.93 + return PREF_SELECTED_READER; 1.94 + } 1.95 +} 1.96 + 1.97 +function safeGetCharPref(pref, defaultValue) { 1.98 + var prefs = 1.99 + Cc["@mozilla.org/preferences-service;1"]. 1.100 + getService(Ci.nsIPrefBranch); 1.101 + try { 1.102 + return prefs.getCharPref(pref); 1.103 + } 1.104 + catch (e) { 1.105 + } 1.106 + return defaultValue; 1.107 +} 1.108 + 1.109 +function FeedConverter() { 1.110 +} 1.111 +FeedConverter.prototype = { 1.112 + classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"), 1.113 + 1.114 + /** 1.115 + * This is the downloaded text data for the feed. 1.116 + */ 1.117 + _data: null, 1.118 + 1.119 + /** 1.120 + * This is the object listening to the conversion, which is ultimately the 1.121 + * docshell for the load. 1.122 + */ 1.123 + _listener: null, 1.124 + 1.125 + /** 1.126 + * Records if the feed was sniffed 1.127 + */ 1.128 + _sniffed: false, 1.129 + 1.130 + /** 1.131 + * See nsIStreamConverter.idl 1.132 + */ 1.133 + convert: function FC_convert(sourceStream, sourceType, destinationType, 1.134 + context) { 1.135 + throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1.136 + }, 1.137 + 1.138 + /** 1.139 + * See nsIStreamConverter.idl 1.140 + */ 1.141 + asyncConvertData: function FC_asyncConvertData(sourceType, destinationType, 1.142 + listener, context) { 1.143 + this._listener = listener; 1.144 + }, 1.145 + 1.146 + /** 1.147 + * Whether or not the preview page is being forced. 1.148 + */ 1.149 + _forcePreviewPage: false, 1.150 + 1.151 + /** 1.152 + * Release our references to various things once we're done using them. 1.153 + */ 1.154 + _releaseHandles: function FC__releaseHandles() { 1.155 + this._listener = null; 1.156 + this._request = null; 1.157 + this._processor = null; 1.158 + }, 1.159 + 1.160 + /** 1.161 + * See nsIFeedResultListener.idl 1.162 + */ 1.163 + handleResult: function FC_handleResult(result) { 1.164 + // Feeds come in various content types, which our feed sniffer coerces to 1.165 + // the maybe.feed type. However, feeds are used as a transport for 1.166 + // different data types, e.g. news/blogs (traditional feed), video/audio 1.167 + // (podcasts) and photos (photocasts, photostreams). Each of these is 1.168 + // different in that there's a different class of application suitable for 1.169 + // handling feeds of that type, but without a content-type differentiation 1.170 + // it is difficult for us to disambiguate. 1.171 + // 1.172 + // The other problem is that if the user specifies an auto-action handler 1.173 + // for one feed application, the fact that the content type is shared means 1.174 + // that all other applications will auto-load with that handler too, 1.175 + // regardless of the content-type. 1.176 + // 1.177 + // This means that content-type alone is not enough to determine whether 1.178 + // or not a feed should be auto-handled. This means that for feeds we need 1.179 + // to always use this stream converter, even when an auto-action is 1.180 + // specified, not the basic one provided by WebContentConverter. This 1.181 + // converter needs to consume all of the data and parse it, and based on 1.182 + // that determination make a judgment about type. 1.183 + // 1.184 + // Since there are no content types for this content, and I'm not going to 1.185 + // invent any, the upshot is that while a user can set an auto-handler for 1.186 + // generic feed content, the system will prevent them from setting an auto- 1.187 + // handler for other stream types. In those cases, the user will always see 1.188 + // the preview page and have to select a handler. We can guess and show 1.189 + // a client handler, but will not be able to show web handlers for those 1.190 + // types. 1.191 + // 1.192 + // If this is just a feed, not some kind of specialized application, then 1.193 + // auto-handlers can be set and we should obey them. 1.194 + try { 1.195 + var feedService = 1.196 + Cc["@mozilla.org/browser/feeds/result-service;1"]. 1.197 + getService(Ci.nsIFeedResultService); 1.198 + if (!this._forcePreviewPage && result.doc) { 1.199 + var feed = result.doc.QueryInterface(Ci.nsIFeed); 1.200 + var handler = safeGetCharPref(getPrefActionForType(feed.type), "ask"); 1.201 + 1.202 + if (handler != "ask") { 1.203 + if (handler == "reader") 1.204 + handler = safeGetCharPref(getPrefReaderForType(feed.type), "bookmarks"); 1.205 + switch (handler) { 1.206 + case "web": 1.207 + var wccr = 1.208 + Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"]. 1.209 + getService(Ci.nsIWebContentConverterService); 1.210 + if ((feed.type == Ci.nsIFeed.TYPE_FEED && 1.211 + wccr.getAutoHandler(TYPE_MAYBE_FEED)) || 1.212 + (feed.type == Ci.nsIFeed.TYPE_VIDEO && 1.213 + wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) || 1.214 + (feed.type == Ci.nsIFeed.TYPE_AUDIO && 1.215 + wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) { 1.216 + wccr.loadPreferredHandler(this._request); 1.217 + return; 1.218 + } 1.219 + break; 1.220 + 1.221 + default: 1.222 + LOG("unexpected handler: " + handler); 1.223 + // fall through -- let feed service handle error 1.224 + case "bookmarks": 1.225 + case "client": 1.226 + try { 1.227 + var title = feed.title ? feed.title.plainText() : ""; 1.228 + var desc = feed.subtitle ? feed.subtitle.plainText() : ""; 1.229 + feedService.addToClientReader(result.uri.spec, title, desc, feed.type); 1.230 + return; 1.231 + } catch(ex) { /* fallback to preview mode */ } 1.232 + } 1.233 + } 1.234 + } 1.235 + 1.236 + var ios = 1.237 + Cc["@mozilla.org/network/io-service;1"]. 1.238 + getService(Ci.nsIIOService); 1.239 + var chromeChannel; 1.240 + 1.241 + // If there was no automatic handler, or this was a podcast, 1.242 + // photostream or some other kind of application, show the preview page 1.243 + // if the parser returned a document. 1.244 + if (result.doc) { 1.245 + 1.246 + // Store the result in the result service so that the display 1.247 + // page can access it. 1.248 + feedService.addFeedResult(result); 1.249 + 1.250 + // Now load the actual XUL document. 1.251 + var aboutFeedsURI = ios.newURI("about:feeds", null, null); 1.252 + chromeChannel = ios.newChannelFromURI(aboutFeedsURI, null); 1.253 + chromeChannel.originalURI = result.uri; 1.254 + chromeChannel.owner = 1.255 + Services.scriptSecurityManager.getNoAppCodebasePrincipal(aboutFeedsURI); 1.256 + } else { 1.257 + chromeChannel = ios.newChannelFromURI(result.uri, null); 1.258 + } 1.259 + 1.260 + chromeChannel.loadGroup = this._request.loadGroup; 1.261 + chromeChannel.asyncOpen(this._listener, null); 1.262 + } 1.263 + finally { 1.264 + this._releaseHandles(); 1.265 + } 1.266 + }, 1.267 + 1.268 + /** 1.269 + * See nsIStreamListener.idl 1.270 + */ 1.271 + onDataAvailable: function FC_onDataAvailable(request, context, inputStream, 1.272 + sourceOffset, count) { 1.273 + if (this._processor) 1.274 + this._processor.onDataAvailable(request, context, inputStream, 1.275 + sourceOffset, count); 1.276 + }, 1.277 + 1.278 + /** 1.279 + * See nsIRequestObserver.idl 1.280 + */ 1.281 + onStartRequest: function FC_onStartRequest(request, context) { 1.282 + var channel = request.QueryInterface(Ci.nsIChannel); 1.283 + 1.284 + // Check for a header that tells us there was no sniffing 1.285 + // The value doesn't matter. 1.286 + try { 1.287 + var httpChannel = channel.QueryInterface(Ci.nsIHttpChannel); 1.288 + // Make sure to check requestSucceeded before the potentially-throwing 1.289 + // getResponseHeader. 1.290 + if (!httpChannel.requestSucceeded) { 1.291 + // Just give up, but don't forget to cancel the channel first! 1.292 + request.cancel(Cr.NS_BINDING_ABORTED); 1.293 + return; 1.294 + } 1.295 + var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed"); 1.296 + } 1.297 + catch (ex) { 1.298 + this._sniffed = true; 1.299 + } 1.300 + 1.301 + this._request = request; 1.302 + 1.303 + // Save and reset the forced state bit early, in case there's some kind of 1.304 + // error. 1.305 + var feedService = 1.306 + Cc["@mozilla.org/browser/feeds/result-service;1"]. 1.307 + getService(Ci.nsIFeedResultService); 1.308 + this._forcePreviewPage = feedService.forcePreviewPage; 1.309 + feedService.forcePreviewPage = false; 1.310 + 1.311 + // Parse feed data as it comes in 1.312 + this._processor = 1.313 + Cc["@mozilla.org/feed-processor;1"]. 1.314 + createInstance(Ci.nsIFeedProcessor); 1.315 + this._processor.listener = this; 1.316 + this._processor.parseAsync(null, channel.URI); 1.317 + 1.318 + this._processor.onStartRequest(request, context); 1.319 + }, 1.320 + 1.321 + /** 1.322 + * See nsIRequestObserver.idl 1.323 + */ 1.324 + onStopRequest: function FC_onStopRequest(request, context, status) { 1.325 + if (this._processor) 1.326 + this._processor.onStopRequest(request, context, status); 1.327 + }, 1.328 + 1.329 + /** 1.330 + * See nsISupports.idl 1.331 + */ 1.332 + QueryInterface: function FC_QueryInterface(iid) { 1.333 + if (iid.equals(Ci.nsIFeedResultListener) || 1.334 + iid.equals(Ci.nsIStreamConverter) || 1.335 + iid.equals(Ci.nsIStreamListener) || 1.336 + iid.equals(Ci.nsIRequestObserver)|| 1.337 + iid.equals(Ci.nsISupports)) 1.338 + return this; 1.339 + throw Cr.NS_ERROR_NO_INTERFACE; 1.340 + }, 1.341 +}; 1.342 + 1.343 +/** 1.344 + * Keeps parsed FeedResults around for use elsewhere in the UI after the stream 1.345 + * converter completes. 1.346 + */ 1.347 +function FeedResultService() { 1.348 +} 1.349 + 1.350 +FeedResultService.prototype = { 1.351 + classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"), 1.352 + 1.353 + /** 1.354 + * A URI spec -> [nsIFeedResult] hash. We have to keep a list as the 1.355 + * value in case the same URI is requested concurrently. 1.356 + */ 1.357 + _results: { }, 1.358 + 1.359 + /** 1.360 + * See nsIFeedResultService.idl 1.361 + */ 1.362 + forcePreviewPage: false, 1.363 + 1.364 + /** 1.365 + * See nsIFeedResultService.idl 1.366 + */ 1.367 + addToClientReader: function FRS_addToClientReader(spec, title, subtitle, feedType) { 1.368 + var prefs = 1.369 + Cc["@mozilla.org/preferences-service;1"]. 1.370 + getService(Ci.nsIPrefBranch); 1.371 + 1.372 + var handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks"); 1.373 + if (handler == "ask" || handler == "reader") 1.374 + handler = safeGetCharPref(getPrefReaderForType(feedType), "bookmarks"); 1.375 + 1.376 + switch (handler) { 1.377 + case "client": 1.378 + var clientApp = prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile); 1.379 + 1.380 + // For the benefit of applications that might know how to deal with more 1.381 + // URLs than just feeds, send feed: URLs in the following format: 1.382 + // 1.383 + // http urls: replace scheme with feed, e.g. 1.384 + // http://foo.com/index.rdf -> feed://foo.com/index.rdf 1.385 + // other urls: prepend feed: scheme, e.g. 1.386 + // https://foo.com/index.rdf -> feed:https://foo.com/index.rdf 1.387 + var ios = 1.388 + Cc["@mozilla.org/network/io-service;1"]. 1.389 + getService(Ci.nsIIOService); 1.390 + var feedURI = ios.newURI(spec, null, null); 1.391 + if (feedURI.schemeIs("http")) { 1.392 + feedURI.scheme = "feed"; 1.393 + spec = feedURI.spec; 1.394 + } 1.395 + else 1.396 + spec = "feed:" + spec; 1.397 + 1.398 + // Retrieving the shell service might fail on some systems, most 1.399 + // notably systems where GNOME is not installed. 1.400 + try { 1.401 + var ss = 1.402 + Cc["@mozilla.org/browser/shell-service;1"]. 1.403 + getService(Ci.nsIShellService); 1.404 + ss.openApplicationWithURI(clientApp, spec); 1.405 + } catch(e) { 1.406 + // If we couldn't use the shell service, fallback to using a 1.407 + // nsIProcess instance 1.408 + var p = 1.409 + Cc["@mozilla.org/process/util;1"]. 1.410 + createInstance(Ci.nsIProcess); 1.411 + p.init(clientApp); 1.412 + p.run(false, [spec], 1); 1.413 + } 1.414 + break; 1.415 + 1.416 + default: 1.417 + // "web" should have been handled elsewhere 1.418 + LOG("unexpected handler: " + handler); 1.419 + // fall through 1.420 + case "bookmarks": 1.421 + var wm = 1.422 + Cc["@mozilla.org/appshell/window-mediator;1"]. 1.423 + getService(Ci.nsIWindowMediator); 1.424 + var topWindow = wm.getMostRecentWindow("navigator:browser"); 1.425 + topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle); 1.426 + break; 1.427 + } 1.428 + }, 1.429 + 1.430 + /** 1.431 + * See nsIFeedResultService.idl 1.432 + */ 1.433 + addFeedResult: function FRS_addFeedResult(feedResult) { 1.434 + NS_ASSERT(feedResult.uri != null, "null URI!"); 1.435 + NS_ASSERT(feedResult.uri != null, "null feedResult!"); 1.436 + var spec = feedResult.uri.spec; 1.437 + if(!this._results[spec]) 1.438 + this._results[spec] = []; 1.439 + this._results[spec].push(feedResult); 1.440 + }, 1.441 + 1.442 + /** 1.443 + * See nsIFeedResultService.idl 1.444 + */ 1.445 + getFeedResult: function RFS_getFeedResult(uri) { 1.446 + NS_ASSERT(uri != null, "null URI!"); 1.447 + var resultList = this._results[uri.spec]; 1.448 + for (var i in resultList) { 1.449 + if (resultList[i].uri == uri) 1.450 + return resultList[i]; 1.451 + } 1.452 + return null; 1.453 + }, 1.454 + 1.455 + /** 1.456 + * See nsIFeedResultService.idl 1.457 + */ 1.458 + removeFeedResult: function FRS_removeFeedResult(uri) { 1.459 + NS_ASSERT(uri != null, "null URI!"); 1.460 + var resultList = this._results[uri.spec]; 1.461 + if (!resultList) 1.462 + return; 1.463 + var deletions = 0; 1.464 + for (var i = 0; i < resultList.length; ++i) { 1.465 + if (resultList[i].uri == uri) { 1.466 + delete resultList[i]; 1.467 + ++deletions; 1.468 + } 1.469 + } 1.470 + 1.471 + // send the holes to the end 1.472 + resultList.sort(); 1.473 + // and trim the list 1.474 + resultList.splice(resultList.length - deletions, deletions); 1.475 + if (resultList.length == 0) 1.476 + delete this._results[uri.spec]; 1.477 + }, 1.478 + 1.479 + createInstance: function FRS_createInstance(outer, iid) { 1.480 + if (outer != null) 1.481 + throw Cr.NS_ERROR_NO_AGGREGATION; 1.482 + return this.QueryInterface(iid); 1.483 + }, 1.484 + 1.485 + QueryInterface: function FRS_QueryInterface(iid) { 1.486 + if (iid.equals(Ci.nsIFeedResultService) || 1.487 + iid.equals(Ci.nsIFactory) || 1.488 + iid.equals(Ci.nsISupports)) 1.489 + return this; 1.490 + throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1.491 + }, 1.492 +}; 1.493 + 1.494 +/** 1.495 + * A protocol handler that attempts to deal with the variant forms of feed: 1.496 + * URIs that are actually either http or https. 1.497 + */ 1.498 +function GenericProtocolHandler() { 1.499 +} 1.500 +GenericProtocolHandler.prototype = { 1.501 + _init: function GPH_init(scheme) { 1.502 + var ios = 1.503 + Cc["@mozilla.org/network/io-service;1"]. 1.504 + getService(Ci.nsIIOService); 1.505 + this._http = ios.getProtocolHandler("http"); 1.506 + this._scheme = scheme; 1.507 + }, 1.508 + 1.509 + get scheme() { 1.510 + return this._scheme; 1.511 + }, 1.512 + 1.513 + get protocolFlags() { 1.514 + return this._http.protocolFlags; 1.515 + }, 1.516 + 1.517 + get defaultPort() { 1.518 + return this._http.defaultPort; 1.519 + }, 1.520 + 1.521 + allowPort: function GPH_allowPort(port, scheme) { 1.522 + return this._http.allowPort(port, scheme); 1.523 + }, 1.524 + 1.525 + newURI: function GPH_newURI(spec, originalCharset, baseURI) { 1.526 + // Feed URIs can be either nested URIs of the form feed:realURI (in which 1.527 + // case we create a nested URI for the realURI) or feed://example.com, in 1.528 + // which case we create a nested URI for the real protocol which is http. 1.529 + 1.530 + var scheme = this._scheme + ":"; 1.531 + if (spec.substr(0, scheme.length) != scheme) 1.532 + throw Cr.NS_ERROR_MALFORMED_URI; 1.533 + 1.534 + var prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : ""; 1.535 + var inner = Cc["@mozilla.org/network/io-service;1"]. 1.536 + getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix), 1.537 + originalCharset, baseURI); 1.538 + var netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil); 1.539 + const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler 1.540 + .URI_INHERITS_SECURITY_CONTEXT; 1.541 + if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT)) 1.542 + throw Cr.NS_ERROR_MALFORMED_URI; 1.543 + 1.544 + var uri = netutil.newSimpleNestedURI(inner); 1.545 + uri.spec = inner.spec.replace(prefix, scheme); 1.546 + return uri; 1.547 + }, 1.548 + 1.549 + newChannel: function GPH_newChannel(aUri) { 1.550 + var inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI; 1.551 + var channel = Cc["@mozilla.org/network/io-service;1"]. 1.552 + getService(Ci.nsIIOService).newChannelFromURI(inner, null); 1.553 + if (channel instanceof Components.interfaces.nsIHttpChannel) 1.554 + // Set this so we know this is supposed to be a feed 1.555 + channel.setRequestHeader("X-Moz-Is-Feed", "1", false); 1.556 + channel.originalURI = aUri; 1.557 + return channel; 1.558 + }, 1.559 + 1.560 + QueryInterface: function GPH_QueryInterface(iid) { 1.561 + if (iid.equals(Ci.nsIProtocolHandler) || 1.562 + iid.equals(Ci.nsISupports)) 1.563 + return this; 1.564 + throw Cr.NS_ERROR_NO_INTERFACE; 1.565 + } 1.566 +}; 1.567 + 1.568 +function FeedProtocolHandler() { 1.569 + this._init('feed'); 1.570 +} 1.571 +FeedProtocolHandler.prototype = new GenericProtocolHandler(); 1.572 +FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}"); 1.573 + 1.574 +function PodCastProtocolHandler() { 1.575 + this._init('pcast'); 1.576 +} 1.577 +PodCastProtocolHandler.prototype = new GenericProtocolHandler(); 1.578 +PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}"); 1.579 + 1.580 +var components = [FeedConverter, 1.581 + FeedResultService, 1.582 + FeedProtocolHandler, 1.583 + PodCastProtocolHandler]; 1.584 + 1.585 + 1.586 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);